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 want there to be multiple solutions to a problem and the program will choose the best ranked solution, which is just chosen by the highest number which gets incremented every time it is correct. For this I have the following SQL statement:
sql = "SELECT Solution FROM dbo.Questions WHERE Rank=(SELECT MAX(Rank) FROM
dbo.Questions) AND QuestionId =" + questionId;
When I had just one solution available this worked fine, but when I have multiple solutions it doesn't.
You need to properly parameterize your queries. Bobby Tables: A guide to preventing SQL injection
sql = "SELECT Solution FROM dbo.Questions q WHERE Rank=(SELECT MAX(Rank) FROM
dbo.Questions i where i.QuestionId = q.QuestionId) AND q.QuestionId =" + questionId;
This makes sure the max(rank) returned by the subquery is the max(rank) of the solution for the QuestionId you are querying for.
You can also do this if you just want one Solution:
select top 1 Solution
from dbo.Questions q
where QuestionId = #QuestionId
order by [Rank] desc
You're selecting the max rank of all solutions and looking for the solution to a specified question with that rank.
Start at the beginning - select the possible solutions into a CTE
with solutions as(
SELECT Solution, Rank FROM dbo.Questions WHERE QuestionId = #questionId
)
... more to come
Given that, you can use a ranking function to rank all the possible solutions, by rank and select the one with the best rank (too many ranks in that sentence!)
with solutions as(
SELECT Solution, Rank() OVER (ORDER BY Rank DESC) as rank
FROM dbo.Questions WHERE QuestionId = #questionId
)
SELECT * FROM solutions WHERE rank = 1
This doesnt cover 2 solutions having an equal rank - you'll get 2 results from the query if 2 have the same rank. Two solutions there - consider using both.
Chose a tiebreaker column - perhaps the most recent - by adding a second column into the ORDER BY (...RANK() OVER (ORDER BY rank DESC, CreatedDate DESC)
Throw a TOP 1 into the final select (SELECT TOP 1 * FROM solutions WHERE rank = 1)
The record has the MAX rank may not have the questionId you given in the AND QuestionId =" + questionId;
You might try this, I have left the where clause outside of the CTE query so that you can also use the query to get a complete list of each question with it's max rank (if you leave out the questionId filter):
with [ctreMaxSolution] as
(
select [QuestionId]
, max([Rank]) as [Rank]
from [dbo].[Questions]
group by [QuestionId]
)
select *
from [dbo].[Questions] as [q]
inner join [cteMaxSolution] as [cms] on [q].[QuestionId] = [cms].[QuestionId]
and [q].[Rank] = [cms].[Rank]
where [q].[QuestionId] = #questionId;
I've used a SQL Server variable there, but you can make a stored procedure out of it or convert it to an ad-hoc query like in your question, totally up to you.
First make a filter by "questionId" and then "order By" rank.
try with this:
SELECT TOP 1 q.solution FROM dbo.Questions q WHERE q.QuestionId = #QuestionId
ORDER BY q.Rank desc
Hope this help!
Kindly modify your query as below.
sql = "SELECT Solution FROM dbo.Questions WHERE Rank in (SELECT MAX(Rank) FROM
dbo.Questions) AND QuestionId =" + questionId;
Here i have modified where rank "in" instead of "="
Related
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
I have the following SQL Table:
Name Description Id UserId CreatedDate
UserSet1 Desc1 1 Abc 06/01/2018
UserSet1 Desc2 2 Def 06/02/2018
UserSet2 Desc for 2 5 NewUser 06/04/2018
UserSet2 Desc for 2 7 NewUser 06/19/2018
What I want to extract from the above table is just the latest Id for each Name so that I could get the following output
Name Description Id UserId CreatedDate
UserSet1 Desc2 2 Def 06/01/2018
UserSet2 Desc for 2 7 NewUser 06/19/2018
Since Id 2 & 7 are the latest entries in the table for UserSet1 & UserSet2, I would like to display that instead of all the entries in the table.
Any inputs how can I get the desired result.
I am open for solutions directly returning the output or any linq (C#) solutions as well. Ie returning the entire dataset and then using linq to filter the above.
EDIT: Since you are looking for the highest number ID, the GROUP BY method would probably be easier to work with.
Using a window function:
SELECT *
FROM (
SELECT Name, Description, Id, UserId, CreatedDate
, ROW_NUMBER() OVER (PARTITION BY Name ORDER BY CreatedDate DESC) AS rn
FROM myTable
) s1
WHERE rn = 1
I don't have an instance of dynamoDB to test on, but I believe it can use the ROW_NUMBER() window function.
Thanks everyone for pointing to right direction. I have got this working with the below code of Linq and C#:
var results = response.GroupBy(row => row.Name)
.SelectMany(g => g.OrderByDescending(row => row.Id).Take(1));
For the initial tests this seems to be working. Let me know if you think this has come issues.
This should be a general SQL answer:
SELECT * FROM yourtable Y1
WHERE Id = (SELECT MAX(Id)
FROM yourtable Y2
WHERE Y2.Name = Y1.Name)
If it was MS SQL you could use Partition By command, otherwise most performant way would be:
select * from Table
where Id in (
select Max(Id) from Table
Group By Name
)
not sure if you can leave Name out of the Select statement, you might need to do:
select * from Table
where Id in (
Select Id from
(select Name, Max(Id) as Id from Table
Group By Name)
)
Given this query, if I want to pull the rank of a specific individual where I know there $name and $score and return the rows above/below that rank (say +/- 4), how would I go about doing that?
$query = "SELECT #curRank := #curRank + 1 AS Rank,
uniqueID,
name,
score
FROM scores, (SELECT #curRank := 0) r
ORDER by score DESC";
I'm coding in php, using MySQL and C# in Unity. My game is making a call to the server and running the php code. Goal is to echo the information and parse the information back in the game.
Any help would be much appreciated :)
Based off of your :=, I'm assuming you are using PostgreSQL, correct? I'm more familiar with the T-SQL syntax; but regardless, both PostgreSQL and T-SQL have windowing functions. You could implement something similar to the following (I left out variables for you to fill-in):
$query = "WITH scoreOrder
AS
(
SELECT uniqueID,
name,
score,
ROW_NUMBER() OVER (ORDER BY score DESC, uniqueID DESC) AS RowNum
FROM scores
ORDER BY uniqueID DESC
)
SELECT ns.*
FROM scoreOrder ms --Your matching score
INNER JOIN scoreOrder ns --Your nearby scores
ON ms.name = /* your name variable */
AND ms.score = /* your score variable */
AND ns.RowNum BETWEEN ms.RowNum - /* your offset */ and ms.RowNum + /* your offset */";
Explanation: First, we're creating a common table expression called scoreOrder, and projecting a RowNum column for your scores. In short, ROW_NUMBER() OVER (ORDER BY score DESC, uniqueID DESC) is just saying, "I am returning the row number of this record ordered by score and uniqueID, both descending and in that order." Then, you join that CTE with itself... ms will be your score that you match with, and you join that with ns where the ns.RowNum will be between your ms.RowNum, plus or minus your offset.
There are a ton of other windowing functions. Here are some others that could be more or less appropriate for your scenario:
ROW_NUMBER() - the rownumber of the record
RANK() - the rank of the record, duplicating in ties and includes
gaps (i.e., if 2nd place ties, you would have 1st, 2nd, 2nd,
4th)
DENSE_RANK() - same as rank, except that it fills in the gaps
(i.e., if 2nd place ties, you would have 1st, 2nd, 2nd, 3rd)
For more info, check the PostgreSQL documentation on windowing functions and their tutorial
Update:
Unfornately, MySQL does not support windowing functions or common table expressions. In your scenario, you will have to put the results of your previous query into a temp table, then doing a similar join as demonstrated above. So for example...
CREATE TEMPORARY TABLE IF NOT EXISTS allRankings AS
(
SELECT #curRank := #curRank + 1 AS Rank,
uniqueID,
name,
score
FROM scores, (SELECT #curRank := 0) r
ORDER by score DESC, uniqueID
);
SELECT r.*
FROM allRankings r
INNER JOIN allRankings myRank
ON r.Rank BETWEEN myRank.Rank - <your offset> AND myRank.Rank + <your offset>
AND myRank.name = <your name>
AND myRank.score = <your score>
ORDER by r.Rank;
Here is a SQLFiddle link for an example. (I'm not using a temp table on SQLFiddle because you have to build tables in the Build Schema window).
In our database we have a table that lacks an identity column. There is an Id column, but it is manually populated when a record is inputted. Any item with an Id over 90,000 is reserved and is populated globally across all customer databases.
I'm building a tool to handle bulk insertions into this table using Entity Framework. I need to figure out what the most efficient method of finding the first available Id is (under 90,000) on the fly without iterating over every single row. It is highly likely that in many of the databases, someone has simply selected a random number that wasn't taken and used it to insert the row.
What is my best recourse?
Edit
After seeing some of the solutions listed, I attempted to replicate the SQL logic in Linq. I doubt it's perfect, but it seems incredibly fast and efficient.
var availableIds = Enumerable.Range(1, 89999)
.Except(db.Table.Where(n => n.Id <= 89999)
.Select(n => n.TagAssociationTypeID))
.ToList();
Have you considered something like:
SELECT
min(RN) AS FirstAvailableID
FROM (
SELECT
row_number() OVER (ORDER BY Id) AS RN,
Id
FROM
YourTable
) x
WHERE
RN <> Id
To answer your implied question of how do you get a list of available numbers to use: Easy, make a list of all possible numbers then delete the ones that are in use.
--This builds a list of numbers from 1 to 89999
SELECT TOP (89999) n = CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id]))
INTO #AvialableNumbers
FROM sys.all_objects AS s1 CROSS JOIN sys.all_objects AS s2
OPTION (MAXDOP 1);
CREATE UNIQUE CLUSTERED INDEX n ON #AvialableNumbers(n)
--Start a seralizeable transaction so we can be sure no one uses a number
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
begin transaction
--Remove numbers that are in use.
delete #AvialableNumbers where n in (select Id from YourTable)
/*
Do your insert here using numbers from #AvialableNumbers
*/
commit transaction
Here is how you would do it via Entity framework
using(var context = new YourContext(connectionString))
using(var transaction = context.Database.BeginTransaction(IsolationLevel.Serializable))
{
var query = #"
SELECT TOP (89999) n = CONVERT(INT, ROW_NUMBER() OVER (ORDER BY s1.[object_id]))
INTO #AvialableNumbers
FROM sys.all_objects AS s1 CROSS JOIN sys.all_objects AS s2
OPTION (MAXDOP 1);
CREATE UNIQUE CLUSTERED INDEX n ON #AvialableNumbers(n)
--Remove numbers that are in use.
delete #AvialableNumbers where n in (select Id from YourTable)
--Select the numbers out to the result set.
select n from #AvialableNumbers order by n
drop table #AvialableNumbers
";
List<int> availableIDs = context.Database.SqlQuery<int>(query).ToList();
/*
Use the list of IDs here
*/
context.SaveChanges();
transaction.Commit();
}
This may be a very silly mistake but I just can't fix it. I have 2 tables, Questions and Questions_Rating.
Questions:
question_id question user_id
------------------------------------------
1 'How to blablabla' 1
2 'bla bla bla' 1
Questions_Rating
In this table, users will rate questions either by +1 or -1
question_rating_id question_id user_id rate
------------------------------------------------------
1 1 2 (+1)
2 1 3 (+1)
3 1 4 ( 1)
Now I would simply like to fetch the question from the questions table, and the SUM of the rate in the questions_rating table for this question, which has an ID = 1.
String QUERY = "SELECT q.question, SUM(r.rate) FROM questions q, questions_rating r WHERE r.question_id = q.question_id AND q.question_id = 1";
And this is what I'm getting.
Column 'questions.question' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
I know that the SUM of the rate will return 1 row, and the question is supposedly 1 row, I can't figure out what's wrong with this query.
I am using SQL Server 2008.
You will have to Group the values as per the question so you need a
GROUP BY q.question
at the end of your query
Good Explanation here
You need the group by q.question in your SQL statement.
Your Sum is suppose to return only a single row against the table, since you are selecting a column along with the Sum you need to specify a group on the selected column. In your case it should be q.question, and sum will be applied to each group of questions.
SELECT q.question,SUM(r.rate) AS RateSum
FROM questions q,questions_rating r
WHERE r.question_id = q.question_id AND q.question_id=1
GROUP BY q.question
or (using ANSI 92 style joins)
SELECT q.question,SUM(r.rate) AS RateSum
FROM questions q INNER JOIN questions_rating r ON r.question_id = q.question_id
WHERE q.question_id=1
GROUP BY q.question
or (using subqueries)
SELECT q.question, (SELECT SUM(r.rate) FROM questions_rating r WHERE r.question_id = q.question_id) AS RateSum
FROM questions q
WHERE q.question_id=1
You are missing group by clause in your select statement.
As you have used sum() function which is an aggregate function
So you need to write group by for all the select columns.
String QUERY = "SELECT
q.question,
SUM(r.rate)
FROM
questions q,
questions_rating r
where
r.question_id = q.question_id AND
q.question_id=1
group by
q.question";