SQL Server Calculated Column using greater than or lesser than - sql-server

I have this table in sql server i wanted to create a calculated field base on the result of the column that i have, i want the result to display this.
column names are
Key,Homework,Quiz,Exam,Result,Grade
Result=>90 "A+,"Result=>80 "A",Result=>70 "B+",Result=>60 "B"

CREATE TABLE dbo.T
(
Val INT,
Chr AS (
CASE
WHEN Val <= 59 THEN ''
WHEN Val BETWEEN 60 AND 69 THEN 'B'
WHEN Val BETWEEN 70 AND 79 THEN 'B+'
WHEN Val BETWEEN 80 AND 89 THEN 'A'
WHEN Val >= 90 THEN 'A+'
END
)
)
INSERT INTO dbo.T(Val) VALUES (100),(80),(75)
SELECT * FROM dbo.T
DROP TABLE IF EXISTS dbo.T

You need to use concept of derived table like below. I am using result column to derive a new column grade using case statements.
create table #tbl(col1 varchar(2), col2 varchar(2), col3 varchar(2), result int)
insert into #tbl(col1,col2,col3,result) values
('A','A','A',10),
('A','A','B',62),
('A','A','C',83),
('A','A','D',94)
SELECT a.col1, a.col2, a.col3, a.result,
case when a.result < 60 then ''
when a.result between 60 AND 69 then 'B'
when a.result between 70 AND 79 then 'B+'
when a.result between 80 AND 89 then 'A'
when a.result > 89 then 'A+'
else NULL
end as grade
FROM
(
Select col1, col2, col3, result
from #tbl
) a
drop table #tbl;

As a computed column...
ALTER TABLE MyTable ADD Grade AS
CASE
WHEN Result < 60 THEN ''
WHEN Result < 70 THEN 'B'
WHEN Result < 80 THEN 'B+'
WHEN Result < 90 THEN 'A'
ELSE 'A+'
END
If you have fractions (which I've seen at the British Open University) you need to rely on CASE evaluation order and <
Using BETWEEN (like other answers) will means a score of 69.5 will not be graded correctly
SQL Server Docs reference
A CASE example

Related

How can I change the code to run the way I want?

I'm trying to solve the "Gradebook Challenge" from KhanAcademy but not in the platform but in SQL Management Studio. And I haven't obtain the same result as on the platform.
This is my code and the error that appears when I try to run it in SQL Management Studio. Yes, I've done the research and now I realise that is not posible use the GROUP BY clause with alias column, but I don't know what else do. So please, I really need a hand on this.
SELECT COUNT(*), Grade,
CASE
WHEN Grade > 90 THEN 'A'
WHEN Grade > 80 THEN 'B'
WHEN Grade > 70 THEN 'C'
ELSE 'F'
END AS LetterGrade
FROM StudentsGrades
GROUP BY Grade
Push the query containing the CASE statement into a subquery or Common Table Expression, eg
use tempdb
go
create table StudentsGrades(Id int identity primary key, StudentId int, Grade int);
insert into StudentsGrades(StudentId,Grade)
values (1,90),(2,99),(3,40),(5,88);
with q as
(
SELECT *,
CASE
WHEN Grade > 90 THEN 'A'
WHEN Grade > 80 THEN 'B'
WHEN Grade > 70 THEN 'C'
ELSE 'F'
END AS LetterGrade
FROM StudentsGrades
)
SELECT LetterGrade, Count(*) CountOfGrade
from q
group by LetterGrade
outputs
LetterGrade CountOfGrade
----------- ------------
A 1
B 2
F 1
(3 rows affected)
Just another option is CROSS APPLY. It allows you to stack calculations and reference the alias.
Example
Select LetterGrade
,CountofGrade = count(*)
From StudentsGrades A
Cross Apply ( values ( CASE WHEN Grade > 90 THEN 'A'
WHEN Grade > 80 THEN 'B'
WHEN Grade > 70 THEN 'C'
ELSE 'F'
END
) )B(LetterGrade)
Group By LetterGrade
Results
LetterGrade CountofGrade
A 1
B 2
F 1

T-SQL (Transact-SQL): Two separate tables. Need the find the date that is >= in Table A based on a date in Table B

Two separate tables. Need the find the date that is >= in Table A based on a date in Table B. Only TransactionCode 59 in Table A should be considered.
From the example tables below my return in table B First_Tran_Date should be "01/22/2022." Table A contains over 35 million records with thousands of AccountNumber's and grows each day.
Need T-SQL to take Table B ChangeDate "01/21/2022" and find the first time Table A shows a TransactionDate on or after that date and only TransactionCode 59 counts. All other TransactionCode dates should not be evaluated for the return.
Table A:
AccountNumber TransactionDate TransactionCode
xxxx310 2/3/2022 40
xxxx310 1/19/2022 40
xxxx310 1/22/2022 59
xxxx310 1/10/2022 59
xxxx310 3/15/2022 40
xxxx310 1/25/2022 59
xxxx310 1/30/2022 40
xxxx310 1/31/2022 59
xxxx310 1/31/2022 62
xxxx310 3/8/2022 59
Table B:
Account ChangeDate First_Tran_Date COUNT_OF_DAYS
xxxx310 01/21/2022 **RESULT NEEDED** (Calculated First_Tran_Date - ChangeDate = COUNT_OF_DAYS)
I have tried the following without getting a correct result:
T-SQL example...
Created a VIEW…
WITH added_row_number AS (
SELECT
*,
ROW_NUMBER() OVER(
PARTITION BY AccountNumber
ORDER BY
TransactionDate
) AS row_number
FROM dbo.LoanTransactions
)
SELECT
*
FROM added_row_number
WHERE row_number = 1
AND TransactionDate >= '2022-03-01'
AND TransactionCode IN ('59', '61', '70', '77', '82') Used a
SELECT
from that VIEW …
SELECT
DISTINCT Account,
Prod_CD,
OldValue,
NewValue,
Acct_Open_DT,
ChangeDate,
LOSVIEW_All_Transactions_From_CORE1.TransactionDate AS First_Tran_Date,
LastTransactionDate,
CASE
WHEN Prod_CD IN ('L50', 'L51', 'L54', 'L77') THEN DATEDIFF(
DAY,
ChangeDate,
LOSVIEW_All_Transactions_From_CORE1.TransactionDate
)
ELSE DATEDIFF(DAY, Acct_Open_DT, ChangeDate)
END AS COUNT_OF_DAYS
FROM dbo.R_InsuranceCodeChanges
LEFT JOIN dbo.LOSVIEW_All_Transactions_From_CORE AS LOSVIEW_All_Transactions_From_CORE1
ON dbo.R_InsuranceCodeChanges.Account = LOSVIEW_All_Transactions_From_CORE1.AccountNumber
WHERE
dbo.R_InsuranceCodeChanges.ChangeDate >= '2022-01-01'
AND dbo.R_InsuranceCodeChanges.NewValue <> '0'
If I understand correctly you want to look up two dates, and also calculate the number of days between them?
Those dates should be the first and last time a row with TransactionCode 59 appears, for a given account, and only including records on or after a given date?
So for the data in your example, the missing date should be 2022-01-22? Then the number of days would be 52 days?
For that I would use OUTER APPLY; which allows you to effectively run a query once for each input row...
SELECT
*,
DATEDIFF(DAY, table_a.MinTransactionDate, table_a.MaxTransactionDate)
FROM
table_b
OUTER APPLY
(
SELECT
MIN(TransactionDate) AS MinTransactionDate,
MAX(TransactionDate) AS MaxTransactionDate
FROM
table_a
WHERE
AccountNumber = table_b.Account
AND TransactionDate >= table_b.ChangeDate
AND TransactionCode = 59
)
AS table_a
Should be an index on tableA.TransactionDate. But I just translated your words to SQL and this is what I got:
select min(TransactionDate) as minDate
from tableA
where TransactionCode = 59
and TransactionDate >= (select max(TransactionDate) from tableB)

Pivot query without knowing the number of columns needed

I have a query that returns the following data.
ProductCode DealRef
1120 23
1120 76
1130 24
Is there a way that if a product code has more than one Deal ref then it will put this into a new column? So the current result would look something like;
ProductCode Deal1 Deal2
1120 23 76
1130 24
If this is not possible then I have an idea that could work. I would do a count on the DealRef column to find out many columns i would need to pivot to. I would then need to add another column to my initial query which will be able to add an id to each row displaying something similar to the below which I'm unsure how to do.
ProductCode DealRef id
1120 23 1
1120 76 2
1130 24 1
You cannot get the fitting number of columns, but you can get as many columns as you expect to be the maximum, most of them beeing NULL:
Paste this into an empty query window and execute. Adapt to your needs
DECLARE #tbl TABLE(ProductCode INT, DealRef INT);
INSERT INTO #tbl VALUES
(1120,23)
,(1120,76)
,(1130,24);
SELECT p.*
FROM
(
SELECT 'deal' + CAST(ROW_NUMBER() OVER(PARTITION BY tbl.ProductCode ORDER BY tbl.ProductCode) AS VARCHAR(10)) AS ColumnName
,tbl.ProductCode
,tbl.DealRef
FROM #tbl AS tbl
) AS x
PIVOT
(
MIN(DealRef) FOR ColumnName IN(deal1,deal2,deal3,deal4 /*Add as many Col-names as you could maximum need*/)
) AS p
Result is
ProductCode deal1 deal2 deal3 deal4
1120 23 76 NULL NULL
1130 24 NULL NULL NULL

Querying emails that contains an individual

I want to pull a conversation for a certain order that a person has ever read a message on. For instance:
Order SentTOID ReadBy
A 111 55
A 55 55
A 111 89
B 111 89
C 111 55
C 55 55
D 111 99
D 99 99
Results
A 111 55
A 55 55
A 111 89
C 111 55
C 55 55
My code will only pull all the cases that were read by 55 but I want the whole converstaion.
Order SentTOID ReadBy
A 111 55
A 55 55
C 111 55
C 55 55
Code used.
Select *
from conversation
where readby = 55.
first get Order and apply IN clause
declare #temp table
(Orders nvarchar(7), SentTOID int, ReadBy int)
insert into #temp values ('A',111,55)
insert into #temp values ('A',55 ,55)
insert into #temp values ('A',111,89)
insert into #temp values ('B',111,89)
insert into #temp values ('C',111,55)
insert into #temp values ('C',55 ,55)
insert into #temp values ('D',111,99)
insert into #temp values ('D',99 ,99)
select * from #temp
where Orders in (select Orders from #temp where ReadBy = 55)
RESULT

Extract multiple numeric values from string with T-SQL

I have a column with data in a special “format”. Example:
L100000
L50
L5
S10
S15L10
S20
S90
S10
S10L5
S10L40
S10L5
The value consists of an “S” and/or an “L”, each with a number following the letter.
I need to write a query, which will return two columns, “S” and “L”, which will have only the coresponding numeric value following the letter.
The above example should look like this:
S L
======== ==========
0 100000
0 50
0 5
10 0
15 10
20 0
90 0
10 0
10 5
10 40
10 5
If no "S" or "L" is found, the default value is zero.
try this:
DECLARE #YourTable table (RowValue varchar(30))
INSERT INTO #YourTable VALUES ('L100000')
INSERT INTO #YourTable VALUES ('L50')
INSERT INTO #YourTable VALUES ('L5')
INSERT INTO #YourTable VALUES ('S10')
INSERT INTO #YourTable VALUES ('S15L10')
INSERT INTO #YourTable VALUES ('S20')
INSERT INTO #YourTable VALUES ('S90')
INSERT INTO #YourTable VALUES ('S10')
INSERT INTO #YourTable VALUES ('S10L5')
INSERT INTO #YourTable VALUES ('S10L40')
INSERT INTO #YourTable VALUES ('S10L5')
SELECT
CASE
WHEN LocationS>0 AND LocationL=0 THEN RIGHT(RowValue,Length-1)
WHEN LocationS>0 THEN SUBSTRING(RowValue,2,LocationL-2)
ELSE NULL
END AS S
,CASE
WHEN LocationS=0 AND LocationL>0 THEN RIGHT(RowValue,Length-1)
WHEN LocationS>0 AND LocationL>0 THEN RIGHT(RowValue,Length-LocationL)
ELSE NULL
END AS L
,RowValue
FROM (SELECT
RowValue
,CHARINDEX('S',RowValue) AS LocationS
,CHARINDEX('L',RowValue) AS LocationL
,LEN(RowValue) AS Length
FROM #YourTable
) dt
OUTPUT
S L RowValue
------------------------------ ------------------------------ --------------
NULL 100000 L100000
NULL 50 L50
NULL 5 L5
10 NULL S10
15 10 S15L10
20 NULL S20
90 NULL S90
10 NULL S10
10 5 S10L5
10 40 S10L40
10 5 S10L5
(11 row(s) affected)
if you have loads of data give this a try, it may be faster (has same output, basically removed the derived table and made everything use inline functions):
SELECT
CASE
WHEN CHARINDEX('S',RowValue)>0 AND CHARINDEX('L',RowValue)=0 THEN RIGHT(RowValue,LEN(RowValue)-1)
WHEN CHARINDEX('S',RowValue)>0 THEN SUBSTRING(RowValue,2,CHARINDEX('L',RowValue)-2)
ELSE NULL
END AS S
,CASE
WHEN CHARINDEX('S',RowValue)=0 AND CHARINDEX('L',RowValue)>0 THEN RIGHT(RowValue,LEN(RowValue)-1)
WHEN CHARINDEX('S',RowValue)>0 AND CHARINDEX('L',RowValue)>0 THEN RIGHT(RowValue,LEN(RowValue)-CHARINDEX('L',RowValue))
ELSE NULL
END AS L
,RowValue
FROM #YourTable
SELECT
SVal = CASE
WHEN PATINDEX('S%L%', TextVal) > 0 THEN REPLACE(LEFT(TextVal, CHARINDEX('L', TextVal) - 1), 'S', '')
WHEN PATINDEX('S%', TextVal) > 0 THEN REPLACE(TextVal, 'S', '')
ELSE '0'
END,
LVal = CASE
WHEN PATINDEX('S%L%', TextVal) > 0 THEN REPLACE(RIGHT(TextVal, LEN(TextVal) - CHARINDEX('L', TextVal)), 'L', '')
WHEN PATINDEX('L%', TextVal) > 0 THEN REPLACE(TextVal, 'L', '')
ELSE '0'
END
FROM StringList
Assumes S always comes before L. Also you might want to cast the results as numbers (they are strings now) depending on what you need for the output.
I would suggest a clr-based function which would use a regex to extract the S or L value from the string. You could use it like this:
insert new_table (s_value, l_value)
select getValue('S', original_value), getValue('L', original_value)
from original_table
not tested but should be close assuming s always appears before l:
select
case when charindex(data, 's') <> 0 then
substr(data, charindex(data, 's'), charindex(data ,'l'))
else 0 end
, case when charindex(data, 'l') <> 0 then
substr(data, charindex(data, 'l'))
else 0 end
from some_table

Resources