Changing position of data in columns depending on actual data - tsql

Suppose I have a table like below
ID Marks1 Marks2 Marks3
-------------------------
1 10 0 4
2 0 40 90
Now, I need to select from this table in a way that will give precedence to positive values first. So if the marks are 0 then it will be shifted to right. The SELECT should give following output
ID Marks1 Marks2 Marks3
-------------------------
1 10 4 0
2 40 90 0
Can you please guide me for the approach? It will be great if it can be done in a select statement itself. Thanks in advance.

Something like this you will need to check for each subsequent row that the previous column isn't 0. Have selected the values out as null as it makes the code slightly easier to read as i can use coalesce
Select
Coalesce(Marks1, Marks2, Marks3,0) as Marks1,
Case when marks1 is not null
then Coalesce(Marks2, Marks3, 0) else 0
end as Marks2,
case when marks1 is not null
and marks2 is not null
then Coalesce(Marks3,0)
end as Marks3
from
(
Select
Case when Marks1 =0 then null else Marks1 end as Marks1,
Case when Marks2 =0 then null else Marks2 end as Marks2,
Case when Marks3 =0 then null else Marks3 end as Marks3
From mytbl
)

Related

Replace calculated negative values with 0 in PostgreSQL

I have a table my_table:
case_id first_created last_paid submitted_time
3456 2021-01-27 2021-01-29 2021-01-26 21:34:36.566023+00:00
7891 2021-08-02 2021-09-16 2022-10-26 19:49:14.135585+00:00
1245 2021-09-13 None 2022-10-31 02:03:59.620348+00:00
9073 None None 2021-09-12 10:25:30.845687+00:00
6891 2021-08-03 2021-09-17 None
I created 2 new variables:
select *,
first_created-coalesce(submitted_time::date) as create_duration,
last_paid-coalesce(submitted_time::date) as paid_duration
from my_table;
The output:
case_id first_created last_paid submitted_time create_duration paid_duration
3456 2021-01-27 2021-01-29 2021-01-26 21:34:36.566023+00:00 1 3
7891 2021-08-02 2021-09-16 2022-10-26 19:49:14.135585+00:00 -450 -405
1245 2021-09-13 null 2022-10-31 02:03:59.620348+00:00 -412 null
9073 None None 2021-09-12 10:25:30.845687+00:00 null null
6891 2021-08-03 2021-09-17 null null null
My question is how can I replace new variables' value with 0, if it is smaller than 0?
The ideal output should look like:
case_id first_created last_paid submitted_time create_duration paid_duration
3456 2021-01-27 2021-01-29 2021-01-26 21:34:36.566023+00:00 1 3
7891 2021-08-02 2021-09-16 2022-10-26 19:49:14.135585+00:00 0 0
1245 2021-09-13 null 2022-10-31 02:03:59.620348+00:00 0 null
9073 None None 2021-09-12 10:25:30.845687+00:00 null null
6891 2021-08-03 2021-09-17 null null null
My code:
select *,
first_created-coalesce(submitted_time::date) as create_duration,
last_paid-coalesce(submitted_time::date) as paid_duration,
case
when create_duration < 0 THEN 0
else create_duration
end as QuantityText
from my_table
greatest(yourvalue,0)
Given yourvalue lower than 0, 0 will be returned as the greater value:
select *,
greatest(0,first_created-coalesce(submitted_time::date)) as create_duration,
greatest(0,last_paid-coalesce(submitted_time::date)) as paid_duration
from my_table
This will also change null values to 0.
case statement
If you wish to keep the null results, you can resort to a regular case statement. In order to alias your calculation you'll have to put it in a subquery or a cte:
select *,
case when create_duration<0 then 0 else create_duration end as create_duration_0,
case when paid_duration<0 then 0 else paid_duration end as paid_duration_0
from (
select *,
first_created-coalesce(submitted_time::date) as create_duration,
last_paid-coalesce(submitted_time::date) as paid_duration
from my_table ) as subquery;
(n+abs(n))/2
If you sum a number with its absolute value, then divide by two (average them out), you'll get that same number if it was positive, or you'll get zero if it was negative because a negative number will always balance itself out with its absolute value:
(-1+abs(-1))/2 = (-1+1)/2 = 0/2 = 0
( 1+abs( 1))/2 = ( 1+1)/2 = 2/2 = 1
select *,
(create_duration + abs(create_duration)) / 2 as create_duration_0,
(paid_duration + abs(paid_duration) ) / 2 as paid_duration_0
from (
select *,
first_created-coalesce(submitted_time::date) as create_duration,
last_paid-coalesce(submitted_time::date) as paid_duration
from my_table ) as subquery;
Which according to this demo, is slightly faster than case and about as fast as greatest(), without affecting null values.
Note that select * pulls everything from below, so you'll end up seeing create_duration as well as create_duration_0 - you can get rid of it by listing your desired output columns explicitly in the outer query. You can also rewrite it without subquery/cte, repeating the calculation, which will look ugly but in most cases planner will notice the repetition and make evaluate it only once
select *,
case when first_created-coalesce(submitted_time::date) < 0
then 0
else first_created-coalesce(submitted_time::date)
end as create_duration,
(abs(last_paid-coalesce(submitted_time::date))+last_paid-coalesce(submitted_time::date))/2 as paid_duration
from my_table ) as subquery;
or using a scalar subquery
select *,
(select case when a<0 then 0 else a end
from (select first_created-coalesce(submitted_time::date)) as alias(a) )
as create_duration,
(select case when a<0 then 0 else a end
from (select last_paid-coalesce(submitted_time::date)) as alias(a) )
as paid_duration
from my_table ) as subquery;
Neither of which help with anything in this case but are good to know.
If you are planning on attaching your SQL Database to an ASP.NET app, you could create a c# script to query your database, and use the following:
Parameters.AddWithValue(‘Data You want to change’ ‘0’);
However, if your not using your SQL database with a ASP.NET app, this will not work.

TSQL - nested case

I ask if the nested houses are used as follows:
SELECT
CASE
WHEN Col1 < 2 THEN
CASE Col2
WHEN 'X' THEN 10
ELSE 11
END
WHEN Col1 = 2 THEN 2
.....
ELSE 0
END as Qty,
......,
FROM ....
explanation: If Col1 <2 shows something, but that something if X gives me the value 10 otherwise 11 If Col1 = 2 shows 2 otherwise 0 everything in the column name Qty
Is the reasoning correct?
Thanks in advance
It's should return what you say you need, but it's easier to read this way:
SELECT
CASE
WHEN Col1 < 2 AND Col2 = 'X' THEN 10
WHEN Col1 < 2 THEN 11
WHEN Col1 = 2 THEN 2
--.....
ELSE 0
END AS Qty
FROM
-- ...

TSQL - replace isnumeric = 0

I have a select statement and in that select statement I have a few columns on which I perform basic calculations (e.g. [Col1] * 3.14). However, occasionally I run into non-numeric values and when that happens, the whole stored procedure fails because of one row.
I've thought about using a WHERE ISNUMERIC(Col1) <> 0, but then I would be excluding information in the other columns.
Is there a way in TSQL to somehow replace all stings with NULL or 0??
Something like...
SELECT blah1, blah2, blah3
CASE WHEN ISNUMERIC(Col1) = 1 THEN [Col1] * 3.14 ELSE NULL END as whatever
FROM your_table
A case can also be made that..
The non-numeric values should be converted to numeric or NULL if that's what's expected in the column, and
If numbers are expected then the column should be a numeric data type in the first place and not a character data type, which allows for these types of errors.
I prefer Try_Cast:
SELECT
someValue
,TRY_CAST(someValue as int) * 3.14 AS TRY_CAST_to_int
,TRY_CAST(someValue as decimal) * 3.14 AS TRY_CAST_to_decimal
,IIF(ISNUMERIC(someValue) = 1, someValue, null) * 3.14 as IIF_IS_NUMERIC
FROM (values
( 'asdf'),
( '2' ),
( '1.55')
) s(someValue)
ISNUMERIC is a terrible way to do this, as there are far too many things that identify as NUMERIC which are not able to be multiplied by a non-MONEY data type.
https://www.brentozar.com/archive/2018/02/fifteen-things-hate-isnumeric/
This fails miserably, as '-' is a numeric...
DECLARE #example TABLE (numerics VARCHAR(10));
INSERT INTO #example VALUES ('-')
SELECT CASE WHEN ISNUMERIC(numerics) = 1 THEN numerics * 3.14 ELSE NULL END
FROM #example;
Try TRY_CAST instead (albeit amend your DECIMAL precision to suit your needs):
DECLARE #example TABLE (numerics VARCHAR(10));
INSERT INTO #example VALUES ('-')
SELECT TRY_CAST(numerics AS decimal(10,2)) * 3.14 FROM #example;
trycast will test for a specfic type
declare #T table (num varchar(20));
insert into #T values ('12'), ('3.14'), ('5.6E12'), ('$120'), ('-'), (''), ('cc'), ('aa'), ('bb'), ('1/5');
select t.num, ISNUMERIC(t.num) as isnumeric
, isnull(TRY_CONVERT(smallmoney, t.num), 0) as smallmoney
, TRY_CONVERT(float, t.num) as float
, TRY_CONVERT(decimal(18,4), t.num) as decimal
, isnull(TRY_CONVERT(smallmoney, t.num), TRY_CONVERT(float, t.num)) as mix
from #T t
num isnumeric smallmoney float decimal
-------------------- ----------- --------------------- ---------------------- ---------------------------------------
12 1 12.00 12 12.0000
3.14 1 3.14 3.14 3.1400
5.6E12 1 0.00 5600000000000 NULL
$120 1 120.00 NULL NULL
- 1 0.00 NULL NULL
0 0.00 0 NULL
cc 0 0.00 NULL NULL
aa 0 0.00 NULL NULL
bb 0 0.00 NULL NULL
1/5 0 0.00 NULL NULL
interesting the last still fails

Multiple Case Statements With Identical Expressions

I'm trying to evaluate a specific column to return five different columns - but the columns are based off the same expression in the CASE statements.
CASE WHEN va.HIN LIKE '%[-=!##$%^&*()<>?:|\;./,]%' THEN 1
ELSE 0
END AS [Invalid] ,
CASE WHEN va.HIN LIKE '%[-=!##$%^&*()<>?:|\;./,]%' THEN 0
ELSE 1
END AS [validMICcode] ,
CASE WHEN va.HIN LIKE '%[-=!##$%^&*()<>?:|\;./,]%' THEN 0
ELSE 1
END AS [validSerialNumber] ,
CASE WHEN va.HIN LIKE '%[-=!##$%^&*()<>?:|\;./,]%' THEN 0
ELSE 1
END AS [validFormat] ,
CASE WHEN va.HIN LIKE '%[-=!##$%^&*()<>?:|\;./,]%' THEN 0
ELSE 1
END AS [validProductionYear] ,
I feel like is causing the table / column in question to be searched for the pattern five times, but I cannot figure out how rewrite it - or if it is even possible - to have one pattern search and define the columns based on the one search.
I'm trying different variations, but I cannot come up with the correct syntax for this issue:
CASE WHEN va.HIN LIKE '%[-=!##$%^&*()<>?:|\;./,]%' THEN 1
ELSE 0
CASE WHEN 1 THEN 1 END AS [Invalid]
CASE WHEN 0 THEN 0 END AS [validMICode]
CASE WHEN 0 THEN 0 END AS AS [validSerialNumber]
CASE WHEN 0 THEN 0 END AS AS [validFormat]
CASE WHEN 0 THEN 0 END AS AS [validProductionYear]
END
One way would be a sub query and Bitwise NOT.
select
result Invalid,
~result validMICcode,
~result validSerialNumber,
~result validFormat,
~result validProductionYear
from
(
select
CASE WHEN va.HIN LIKE '%[-=!##$%^&*()<>?:|\;./,]%'
THEN CAST(1 As bit)
ELSE CAST(0 As bit)
End result
from ...
) tbl
You could do that with a CTE..
with cte as(
select *,
CASE
WHEN va.HIN LIKE '%[-=!##$%^&*()<>?:|\;./,]%'
THEN 1
ELSE 0
END as SomeColumn)
select *,
CASE WHEN SomeColumn = 1 THEN 1 END AS [Invalid]
CASE WHEN SomeColumn = 0 THEN 0 END AS [validMICode]
...
from cte

T SQL Query about grouping

First image is my query output. Now I want to group the subject so that it become like the second image. Is it possible? Thanks for the help.
select Subject, Grade,
case when Grade >= 50
Then '1'
else '0'
end as Pass,
case when Grade < 50
Then '1'
else '0'
end as Fail
from Grade_report
OUTPUT:
what I want is:
Your specification is not very exact; what number do you expect in the grade on the merged record, and should it just write 1 or 0 or aggregate the sum of the pass and fail columns?
The following will generate your output, it takes the MAX for the grade and SUM for pass/fail information:
WITH GradePassFail AS (
SELECT
Subject,
Grade,
CASE WHEN Grade >= 50 THEN 1 ELSE 0 END AS Pass,
CASE WHEN Grade < 50 THEN 1 ELSE 0 END AS Fail
FROM Grade_report
)
SELECT Subject, MAX(Grade) AS Grade, SUM(Pass) AS Pass, SUM(Fail) AS Fail
FROM GradePassFail
GROUP BY Subject