Select row number inside a select query SQL Server - sql-server

I have a Select query which returns data based on highest mark received as follows.
SELECT
Name, Mark,
ROW_NUMBER() OVER (ORDER BY Marks) AS Rank
FROM
table_1
WHERE
IsDelete ='false'
Result:
Rank
Name
Mark
1
User1
10
2
User2
8
3
User11
6
I have another query which returns data from same table which have name matching to search text as follows.
SELECT
FROM table_1
WHERE name LIKE '%' + #SearchText + '%'
ORDER BY Marks
Name
Mark
User1
10
User2
8
User11
6
I need the row number of each candidates based on their marks and matching the search text given in a single query.
The result should be like this when I enter 'r1' as search text
Rank
Name
Mark
1
User1
10
3
User11
6

Another way to do this is with a CTE (common table expression), which often improves the readability (at least for me): e.g.
WITH HighestMarks (Name, Mark, Rank)
AS (
SELECT Name, Mark,
ROW_NUMBER() OVER (ORDER BY Marks) AS Rank
FROM table_1
WHERE IsDelete ='false'
)
SELECT * FROM HighestMarks WHERE name LIKE '%'+#SearchText+'%' ORDER BY Mark

Use a subquery:
select t.*
from (select Name, Mark, ROW_NUMBER() OVER(ORDER BY Marks) as Rank
from table_1
where IsDelete = 'false'
) t
where name like '%' + #SearchText + '%';
Note: You should not be munging the query with values such as #SearchText. Use parameters!!

Related

How to skip the max function which has only one entry when i do a group by in SQL Server

I have a requirement where I do a group by the table
Table
Name salary
------------
abc 10000
abc 1000
def 100
Query:
select max(salary)
from table
group by Name
Result:
abc 10000
def 100
I don't want 'def' to be displayed since it's a single entry in the table. How can I achieve this?
You can add a HAVING clause.
Having specifies a search condition for a group or an aggregate.
HAVING can be used only with the SELECT statement. HAVING is typically
used with a GROUP BY clause. When GROUP BY is not used, there is an
implicit single, aggregated group.
select
Name
,max(salary)
from table
group by Name having count(*) > 1
This will only return the aggregates for names that have more than 1 row, which seems to be what you want.
EXAMPLE
declare #table table (name varchar(16), salary int)
insert into #table
values
('abc',10000),
('abc',1000),
('def',100),
('xxf',100)
select
Name
,max(salary)
from #table
group by Name
having count(*) > 1

How to check for a specific condition by looping through every record in SQL Server?

I do have following table
ID Name
1 Jagan Mohan Reddy868
2 Jagan Mohan Reddy869
3 Jagan Mohan Reddy
Name column size is VARCHAR(55).
Now for some other task we need to take only 10 varchar length i.e. VARCHAR(10).
My requirement is to check that after taking the only 10 bits length of Name column value for eg if i take Name value of ID 1 i.e. Jagan Mohan Reddy868 by SUBSTRING(Name, 0,11) if it equals with another row value. here in this case the final value of SUBSTRING(Jagan Mohan Reddy868, 0,11) is equal to Name value of ID 3 row whose Name is 'Jagan Mohan Reddy'. I need to make a list of those kind rows. Can somebody help me out on how can i achieve in SQL Server.
My main check is that the truncated values of my Name column should not match with any non truncated values of Name column. If so i need to get those records.
Assuming I understand the question, I think you are looking for something like this:
Create and populate sample data (Please save us this step in your future questions)
DECLARE #T as TABLE
(
Id int identity(1,1),
Name varchar(15)
)
INSERT INTO #T VALUES
('Hi, I am Zohar.'),
('Hi, I am Peled.'),
('Hi, I am Z'),
('I''m Zohar peled')
Use a cte with a self inner join to get the list of ids that match the first 10 chars:
;WITH cte as
(
SELECT T2.Id As Id1, T1.Id As Id2
FROM #T T1
INNER JOIN #T T2 ON LEFT(T1.Name, 10) = t2.Name AND T1.Id <> T2.Id
)
Select the records from the original table, inner joined with a union of the Id1 and Id2 from the cte:
SELECT T.Id, Name
FROM #T T
INNER JOIN
(
SELECT Id1 As Id
FROM CTE
UNION
SELECT Id2
FROM CTE
) U ON T.Id = U.Id
Results:
Id Name
----------- ---------------
1 Hi, I am Zohar.
3 Hi, I am Z
Try this
SELECT Id,Name
FROM(
SELECT *,ROW_NUMBER() OVER(PARTITION BY Name, LEFT(Name,11) ORDER BY ID) RN
FROM Tbale1 T
) Tmp
WHERE Tmp.RN = 1
loop over your column for all the values and put your substring() function inside this loop and I think in Sql index of string starts from 1 instead of 0. If you pass your string to charindex() like this
CHARINDEX('Y', 'Your String')
thus you will come to know whether it is starting from 0 or 1
and you can save your substring value as value of other column with length 10
I hope it will help you..
I think this should cover all the cases you are looking for.
-- Create Table
DECLARE #T as TABLE
(
Id int identity(1,1),
Name varchar(55)
)
-- Create Data
INSERT INTO #T VALUES
('Jagan Mohan Reddy868'),
('Jagan Mohan Reddy869'),
('Jagan Mohan Reddy'),
('Mohan Reddy'),
('Mohan Reddy123551'),
('Mohan R')
-- Get Matching Items
select *, SUBSTRING(name, 0, 11) as ShorterName
from #T
where SUBSTRING(name, 0, 11) in
(
-- get all shortnames with a count > 1
select SUBSTRING(name, 0, 11) as ShortName
from #T
group by SUBSTRING(name, 0, 11)
having COUNT(*) > 1
)
order by Name, LEN(Name)

T-SQL Multi-layered CTE query with Aggregates

I have a long Common Table Expression (CTE) query which is trying to calculate percent difference between each users' average score and group average score.
I would like for my multi-layered CTE query to filter and reduce bulk of records down to the following table:
UserID Tag UserAvg GroupAvg PercentDifference
1 Cat 72.50 73 -0.68
2 Cat 75.50 73 3.36
3 Cat 75 73 2.70
4 Cat 73.25 73 0.34
5 Cat 52.3333 73 -32.97
6 Cat 86.25 73 16.64
My problem is getting GroupAvg column so that I can perform % Difference calculation.
To illustrate the current approach I am using; here is the summary of my CTE query:
WITH
-- select 1st 3 columns
UserScores AS (select UserID, Tag, Score FROM {multiple-table} WHERE Tag = 'Cat'),
-- add UserAvg column by grouping records
ScoreAverages AS (select UserID, Tag, AVG(Score) AS UserAvg GROUP BY UserID, Tag FROM UserScores),
-- calculate GroupAvg
GroupAverage AS (select AVG(UserAvg) AS GroupAvg FROM ScoreAverages),
-- calculate % difference
PercentDiff AS (select UserID, Tag, UserAvg, 73 AS GroupAvg, (((UserAvg-73)/((UserAvg+73)/2))*100) AS PercentDifference FROM ScoreAverages )
-- do something with results
select * from PercentDiff
Simple enough; right?
Notice that I have hard coded 73 as my GroupAvg value. I am unsure how to construct required sql query that would allow me to go from ScoreAverages to PercentDiff table.
Is it possible to perform SELECT within a SELECT statement? And I am not looking for something of the following:
select * from X where Id in (select Id from Y where Name like '%abc%')
Or I am simply trying to do too much in one go?
Yes, it's called a sub-select:
SELECT Column1, Column2, (SELECT QUERY THAT GETS GROUP AVERAGE) AS GroupAverage, Column3
FROM ...
To use the result of the sub-select in another column's calculation, you can either repeat the sub-select:
SELECT Column1, Column2, (SELECT QUERY THAT GETS GROUP AVERAGE) AS GroupAverage, (Column3 - (SELECT QUERY THAT GETS GROUP AVERAGE)) AS Column4
FROM ...
Or you can reference it the same as you would any other column in the outer query or a subsequent CTE:
WITH CTE1 AS (SELECT Column1, Column2, (SELECT QUERY THAT GETS GROUP AVERAGE) AS GroupAverage
FROM ...)
, CTE2 AS (SELECT *, Column3-GroupAverage) AS Column4
FROM CTE1
JOIN ...
It is possible, as shown in Tab Alleman's answer, but in your case it's not necessary. Since you already calculate the GroupAvg in the cte chain, you can use it in the final query. and since the GroupAverage only contains one row, you can simply add a CROSS JOIN to it:
;WITH
-- select 1st 3 columns
UserScores AS (
select UserID, Tag, Score
FROM {multiple-table}
WHERE Tag = 'Cat'),
-- add UserAvg column by grouping records
ScoreAverages AS (
select UserID, Tag, AVG(Score) AS UserAvg
FROM UserScores
GROUP BY UserID, Tag),
-- calculate GroupAvg
GroupAverage AS (
select AVG(UserAvg) AS GroupAvg
FROM ScoreAverages),
-- calculate % difference
PercentDiff AS (
select UserID, Tag, UserAvg, GroupAvg,
(((UserAvg-GroupAvg)/((UserAvg+GroupAvg)/2))*100) AS PercentDifference
FROM ScoreAverages
CROSS JOIN GroupAverage)
-- do something with results
select * from PercentDiff
I just thought you could do this with a single cte like so.
;WITH UserAverages AS
(
SELECT UserID,
Tag,
AVG(Score) AS UserAvg,
AVG(AVG(Score)) OVER () AS GroupAvg
FROM {multiple-table}
WHERE Tag = 'Cat'
GROUP BY UserID, Tag
)
SELECT UserID,
Tag,
UserAvg,
GroupAvg,
(((UserAvg-GroupAvg)/((UserAvg+GroupAvg)/2))*100) AS PercentDifference
FROM UserAverages

where not in / where not like subquery

Can somebody help me out with a MS-SQL query please.
I have the following:
select Name from Keyword.dbo.NGrams
where Name not in (select Name from Keyword.dbo.Brands)
What I really want is something like this, but I can't get the syntax right
select Name from Keyword.dbo.NGrams
where Name not like (select Name from Keyword.dbo.Brands)
"not in" works great for NGrams & Brands that match exactly. But my NGrams are multiple words long and some contain a Brand within them.
Thanks so much
Edit: Maybe I can re-clarify what I am looking for my this pseudo sql:
select Name from Keyword.dbo.NGrams
where Description not containing (select Word from Keyword.dbo.Brands)
Brand is a list of single words. Description in NGrams would be a 2 or 3 word phrase. I want to select all the NGrams that do not contain any of the Brands
SELECT
n.Name
FROM Keyword.dbo.NGrams n
LEFT JOIN Keyword.dbo.Brands b
ON n.Name LIKE '%'+b.Name+'%'
WHERE b.Name IS NULL
SQL Fiddle Demo
If you want to avoid the Scunthorpe Problem and only match whole words, change the join condition to:
ON ' '+n.Name+' ' LIKE '% '+b.Name+' %'
Use a where not exists to express the like:
select Name
from Keyword.dbo.NGrams ng
where not exists (
select *
from Keyword.dbo.Brands b
where ng.Name like '%' + b.name + '%'
)
I ran a test using the ENABLE2K standard English word list. I generated 10 million random ngrams and 50000 random brands. The query takes about 1 minute to run on my workstation.
CREATE TABLE #enable2k (word varchar(max) NOT NULL)
BULK INSERT #enable2k FROM 'C:\enable2k.txt'
CREATE TABLE #ngrams (ngram_id int NOT NULL, word_num int NOT NULL, word varchar(max) NOT NULL, PRIMARY KEY(ngram_id, word_num));
INSERT #ngrams SELECT TOP 10000000 ROW_NUMBER() OVER(ORDER BY NEWID()), 1, word FROM #enable2k,(SELECT TOP 58 0 FROM master..spt_values) t(i)
INSERT #ngrams SELECT TOP 10000000 ROW_NUMBER() OVER(ORDER BY NEWID()), 2, word FROM #enable2k,(SELECT TOP 58 0 FROM master..spt_values) t(i)
INSERT #ngrams SELECT TOP 10000000 ROW_NUMBER() OVER(ORDER BY NEWID()), 3, word FROM #enable2k,(SELECT TOP 58 0 FROM master..spt_values) t(i)
CREATE TABLE #brands (brand varchar(32) NOT NULL PRIMARY KEY)
INSERT #brands SELECT TOP 50000 word FROM #enable2k WHERE LEN(word) <= 32 ORDER BY NEWID()
SELECT *
FROM #ngrams n
PIVOT (MIN(word) FOR word_num IN ([1],[2],[3])) n1
WHERE NOT EXISTS (
SELECT 1
FROM #ngrams n2
INNER JOIN #brands b
ON (n2.word = b.brand)
WHERE n1.ngram_id = n2.ngram_id
)

SQL Server: Joining in rows via. comma separated field

I'm trying to extract some data from a third party system which uses an SQL Server database. The DB structure looks something like this:
Order
OrderID OrderNumber
1 OX101
2 OX102
OrderItem
OrderItemID OrderID OptionCodes
1 1 12,14,15
2 1 14
3 2 15
Option
OptionID Description
12 Batteries
14 Gift wrap
15 Case
[etc.]
What I want is one row per order item that includes a concatenated field with each option description. So something like this:
OrderItemID OrderNumber Options
1 OX101 Batteries\nGift Wrap\nCase
2 OX101 Gift Wrap
3 OX102 Case
Of course this is complicated by the fact that the options are a comma separated string field instead of a proper lookup table. So I need to split this up by comma in order to join in the options table, and then concat the result back into one field.
At first I tried creating a function which splits out the option data by comma and returns this as a table. Although I was able to join the result of this function with the options table, I wasn't able to pass the OptionCodes column to the function in the join, as it only seemed to work with declared variables or hard-coded values.
Can someone point me in the right direction?
I would use a splitting function (here's an example) to get individual values and keep them in a CTE. Then you can join the CTE to your table called "Option".
SELECT * INTO #Order
FROM (
SELECT 1 OrderID, 'OX101' OrderNumber UNION SELECT 2, 'OX102'
) X;
SELECT * INTO #OrderItem
FROM (
SELECT 1 OrderItemID, 1 OrderID, '12,14,15' OptionCodes
UNION
SELECT 2, 1, '14'
UNION
SELECT 3, 2, '15'
) X;
SELECT * INTO #Option
FROM (
SELECT 12 OptionID, 'Batteries' Description
UNION
SELECT 14, 'Gift Wrap'
UNION
SELECT 15, 'Case'
) X;
WITH N AS (
SELECT I.OrderID, I.OrderItemID, X.items OptionCode
FROM #OrderItem I CROSS APPLY dbo.Split(OptionCodes, ',') X
)
SELECT Q.OrderItemID, Q.OrderNumber,
CONVERT(NVarChar(1000), (
SELECT T.Description + ','
FROM N INNER JOIN #Option T ON N.OptionCode = T.OptionID
WHERE N.OrderItemID = Q.OrderItemID
FOR XML PATH(''))
) Options
FROM (
SELECT N.OrderItemID, O.OrderNumber
FROM #Order O INNER JOIN N ON O.OrderID = N.OrderID
GROUP BY N.OrderItemID, O.OrderNumber) Q
DROP TABLE #Order;
DROP TABLE #OrderItem;
DROP TABLE #Option;

Resources