Learning how to use SQL, how to fix isNull error? - sql

I'm trying to test some codes in SQL, in this case one being to calculate the Amount Due.
Our database has a field that calculates it through this expression:
round(total_amount - if( isNull( amount_paid ), 0, amount_paid )
- if( isNull( terms_taken ), 0, terms_taken )
- if( isNull( tax_terms_taken ), 0, tax_terms_taken )
- if( isNull( allowed ), 0, allowed )
+ if( isNull( memo_amount ), 0, memo_amount )
+ if( isNull( bad_debt_amount ), 0, bad_debt_amount ),
2)
However when I try to use Excel to query this from the database, it states that the isNull function requires 2 arguments.
What would be the best way to achieve this?

Excel isNULL expects single argument where as SQL ISNULL expects
two arguments.
What you have shown us is EXCEL query. But won't
work in SQL. In SQL, you will need to replace if(isnull(..), , ) with isnull(..., VALUE_THAT_YOU_WANT_TO_USE_WHEN_IT_IS_NULL). So your SQL equivalent query is:
round(total_amount
- isNull( amount_paid, 0)
- isNull( terms_taken, 0 )
- isNull( tax_terms_taken, 0)
- isNull( allowed, 0 )
+ isNull( memo_amount, 0)
+ isNull( bad_debt_amount, 0 )
, 2)

Related

Is it possible to find the first occurrence of a string that's NOT within a set of delimiters in SQL Server 2016+?

I have a column in a SQL Server table that has strings of varying lengths. I need to find the position of the first occurrence of the string , -- that's not enclosed in single quotes or square brackets.
For example, in the following two strings, I've bolded the portion I would like to get the position of. Notice in the first string, the first time , -- appears on its own (without being between single quote or square bracket delimiters) is at position 13 and in the second string, it's at position 16.
'a, --'[, --]**, --**[, --]
[a, --b]aaaaaaa_ **, --**', --'
Also I should mention that , -- itself could appear multiple times in the string.
Here's a simple query that shows the strings and my desired output.
SELECT
t.string, t.desired_pos
FROM
(VALUES (N'''a, --''[, --], --[, --]', 14),
(N'[a, —-b]aaaaaaa_ , --'', --''', 18)) t(string, desired_pos)
Is there any way to accomplish this using a SELECT query (or multiple) without using a function?
Thank you in advance!
I've tried variations of SUBSTRING, CHARINDEX, and even some CROSS APPLYs but I can't seem to get the result I'm looking for.
Before i write down my solution, i must warn you: DON'T USE IT. Use a function, or do this in some other language. This code is probably buggy.
It doesn't handle stuff like escaped quotes etcetc.
The idea is to first remove the stuff inside brackets [] and quotes '' and then just do a "simple" charindex.
To remove the brackets, i'm using a recursive CTE that loops ever part of matching quotes and replaces their content with placeholder strings.
One important point is that quotes might be embedded in each other, so you have to try both variants and chose the one that is earliest.
WITH CTE AS (
SELECT *
FROM
(VALUES (N'''a, --''[, --], --[, --]', 14),
(N'[a, —-b]aaaaaaa_ , --'', --''', 18)) t(string, desired_pos)
)
, cte2 AS (
select x.start
, x.finish
, case when x.start > 0 THEN STUFF(string, x.start, x.finish - x.start + 1, REPLICATE('a', x.finish - x.start + 1)) ELSE string END AS newString
, 1 as level
, string as orig
, desired_pos
from cte
CROSS APPLY (
SELECT *
, ROW_NUMBER() OVER(ORDER BY case when start > 0 THEN 0 ELSE 1 END, start) AS sortorder
FROM (
SELECT charindex('[', string) AS start
, charindex(']', string) AS finish
UNION ALL
SELECT charindex('''', string) AS startQ
, charindex('''', string, charindex('''', string) + 1) AS finishQ
) x
) x
WHERE x.sortorder = 1
UNION ALL
select x.start
, x.finish
, STUFF(newString, x.start, x.finish - x.start + 1, REPLICATE('a', x.finish - x.start + 1))
, 1 as level
, orig
, desired_pos
from cte2
CROSS APPLY (
SELECT *
, ROW_NUMBER() OVER(ORDER BY case when start > 0 THEN 0 ELSE 1 END, start) AS sortorder
FROM (
SELECT charindex('[', newString) AS start
, charindex(']', newString) AS finish
UNION ALL
SELECT charindex('''', newString) AS startQ
, charindex('''', newString, charindex('''', newString) + 1) AS finishQ
) x
) x
WHERE x.sortorder = 1
AND x.start > 0
AND cte2.start > 0 -- Must have been a match
)
SELECT PATINDEX('%, --%', newString), *
from (
select *, row_number() over(partition by orig order by level desc) AS sort
from cte2
) x
where x.sort = 1
Try this approach. I'm replacing the strings you don't need for another string of the same length. Then look for the position of the interested string.
SELECT string, desired_pos,
CHARINDEX(', --', REPLACE(REPLACE(string, ''', --''', '******'), '[, --]', '******')
) start_index
FROM (VALUES (N''', --''[, --], --[, --]', 13),
(N'[, --]aaaaaaa_ , --'', --''', 16)) t(string, desired_pos)
I don't know if it makes sense with a C# solution, but this class for CVS is a nice little parcer: TextFieldParser
Then you just define Delimeters etc. and assuming the input is escaped consistently then all is good.
Im late the game here but This kind of thing is simple in SQL Server when leveraging NGrams8k. Not only do you not need REGEX, a CLR, C# required. Furthermore, NGrams8k will be the fastest by far. In 8 years nobody has produced anything remotely as fast. Furthermore, this code will be faster and far less complex than a recursive CTE solution (which are almost always slow in SQL Server)
;--==== Sample Data
DECLARE #T Table (String VARCHAR(100))
INSERT #T
VALUES (N'''a, --''[, --], --[, --]'),
(N'[a, —-b]aaaaaaa_ , --'', --''');
;--==== Solution
SELECT
t.String, ng.Position
FROM #t AS t
CROSS APPLY (VALUES(REPLACE(t.String,'[',CHAR(1)))) AS f(S)
CROSS APPLY samd.NGrams8k(f.S,4) AS ng
CROSS APPLY (VALUES(SUBSTRING(f.S,ng.Position-2,7))) AS g(String)
WHERE ng.Token = ', --'
AND g.String NOT LIKE '%''%''%'
AND g.String NOT LIKE '%'+CHAR(1)+'%]%';
Results:
String Position
----------------------------- --------------------
'a, --'[, --], --[, --] 14
[a, —-b]aaaaaaa_ , --', --' 18

Multiple IIFs and CHARINDEXs searches to extract data from same column in SQL?

This is MS SQL Server 2017.
This currently works. I just can't believe that this is the best way to perform these actions.
The Meeting table is populated from multiple services. It has a MeetingComment column that we used to encode some additional information from some of those sources but is unused by other sources. I want to extract some of the coded information, when used, into separate columns: Source and MeetingType.
MeetingComment
Source
MeetingType
{{{[ASTRA][CLASS][TMR:OFF]}}} ...
ASTRA
CLASS
{{{[ASTRA][CLASS][MERGED][TMR:OFF]}}} ...
ASTRA
CLASS
{{{[ASTRA][EVENT:Study Session][TMR:OFF]}}} ...
ASTRA
EVENT:Study Session
{{{[ASTRA][EVENT:Meeting][TMR:OFF]}}} ...
ASTRA
EVENT:Meeting
{{{[ASTRA][EVENT:Maintenance][TMR:ON]}}} ...
ASTRA
EVENT:Maintenance
UNK
UNK
Here is the SQL that I currently have that is working:
SELECT
Meeting.MeetingID,
Meeting.MeetingComment,
Meeting.Subject,
Rooms.RoomName,
IIF(
CHARINDEX(
'{{{[', Meeting.MeetingComment
) = 1,
SUBSTRING(
Meeting.MeetingComment,
5,
CHARINDEX(
']', Meeting.MeetingComment
)-5
),
'UNK'
) AS Source,
IIF(
CHARINDEX(
'{{{[', Meeting.MeetingComment
) = 1,
SUBSTRING(
Meeting.MeetingComment,
CHARINDEX(
'[',
Meeting.MeetingComment,
(
CHARINDEX(
'[', Meeting.MeetingComment,
1
)
) + 2
) + 1,
CHARINDEX(
']',
Meeting.MeetingComment,
(
CHARINDEX(
']', Meeting.MeetingComment,
1
)
)+ 2
)- CHARINDEX(
'[',
Meeting.MeetingComment,
(
CHARINDEX(
'[', Meeting.MeetingComment,
1
)
) + 2
) -1
),
'UNK'
) AS MeetingType,
Meeting.Recurrence,
Meeting.Location
But, as a programmer, it bugs me to have to use the same conditional test (the IIF statements) for both fields and to have to do the same CHARINDEX lookups multiple times. So before I move on, I just wanted to check to see if there is a better way to do this. Thanks in advance.
It appears from your example you are simply after the content of the first and second set of brackets.
Assuming the current version of SQL Server a more elegant solution would be a little parsing with json conbined with conditional aggregation:
select MeetingComment, Max([Source]) [Source], Max(MeetingType) MeetingType
from t
cross apply (
select
case when [key]='1' then j.[value] end [Source],
case when [key]='3' then j.[value] end MeetingType
from OpenJson(Concat('["', replace(Translate(MeetingComment,'[]',',,'), ',', '","'), '"]')) j
)s
group by MeetingComment;

Table totals to scalar variable in HANA

I'm currently working with writing database procedures for HANA via ABAP objects. I'd like to return a scalar value which is calculated from a selection rather than a table which the other developer would have to read from a table. I'd prefer that I'm not declaring variables to use in the store procedure through the importing/exporting parameters.
methods: _amdp_previous_years
importing value(mandt) type mandt
value(in_object) type j_objnr
value(in_year) type gjahr
exporting value(out_results) type total_table
value(out_total) type f.
method _amdp_previous_years by database procedure for hdb
language sqlscript options read-only
using rpsco.
declare totals double array;
declare out_array double array;
-- begin of totals,
-- total type float,
-- end of totals,
-- out_results = type table of totals
out_results = select sum( wlp01 ) + sum( wlp02 ) + sum( wlp03 ) + sum( wlp04 ) + sum( wlp05 ) +
sum( wlp06 ) + sum( wlp07 ) + sum( wlp08 ) + sum( wlp09 ) + sum( wlp10 ) +
sum( wlp11 ) + sum( wlp12 ) + sum( wlp13 ) + sum( wlp14 ) + sum( wlp15 ) +
sum( wlp16 ) as total from rpsco
where objnr = :in_object
and gjahr = :in_year;
totals := array_agg( :out_results.total );
out_total := :totals[1];
-- Type not declared
-- in sap = wlp01 = curr(15,2)
-- total is not a decimal
-- total is not a double
-- total is not a float
-- total is not a int
-- total is not a real
-- what is total supposed to be then?
results = select sum( wlp01 ) + sum( wlp02 ) + sum( wlp03 ) + sum( wlp04 ) + sum( wlp05 ) +
sum( wlp06 ) + sum( wlp07 ) + sum( wlp08 ) + sum( wlp09 ) + sum( wlp10 ) +
sum( wlp11 ) + sum( wlp12 ) + sum( wlp13 ) + sum( wlp14 ) + sum( wlp15 ) +
sum( wlp16 ) as total from rpsco
where objnr = :in_object
and gjahr = :in_year;
out_array := array_agg( :results.total );
endmethod.
The first statement works ok, I'm guessing because the result of the selection gets placed into a field that is declared as an ABAP float.
The second selection works and results is populated, however I'm not sure how to access the columns. The SAP data element is a CURRENCY field (15,2). I've tried all of the scalar types in the documentation. I received the same error that it is not the correct type.
Is this not possible because the type isn't explicitly defined before hand? While looking around the internet at tutorials people suggest using CREATE TYPE or CREATE TABLE, but I receive syntax errors when trying to use these statements.
I can answer this myself in case anybody else stumbles upon this. You can typecast the columns via various functions such as to_double( ), to_integer( ), and so forth. Now the selection looks like:
results = select to_double( sum( wlp01 ) + sum( wlp02 ) + sum( wlp03 ) + sum( wlp04 ) + sum( wlp05 ) +
sum( wlp06 ) + sum( wlp07 ) + sum( wlp08 ) + sum( wlp09 ) + sum( wlp10 ) +
sum( wlp11 ) + sum( wlp12 ) + sum( wlp13 ) + sum( wlp14 ) + sum( wlp15 ) +
sum( wlp16 ) ) as total from rpsco
where objnr = :in_object
and gjahr < :in_year;

Remove Leading Zeros On Ip Address in SQL

I have a column called IP with data such as 10.001.99.108
I want to run a script to change it to look like 10.1.99.108
I have used this before:
update TABLE set IP = substring(IP, patindex('%[^0]%',IP), 10)
but that removes leading zeros at the begging.
Im not sure how I could change it to do the second segment.
You can do this with parsename() and a method to remove the leading zeros. The following removes the leading zeros by casting to an integer and then back to string:
select (cast(cast(parsename(ip, 4) as int) as varchar(255)) +
cast(cast(parsename(ip, 3) as int) as varchar(255)) +
cast(cast(parsename(ip, 2) as int) as varchar(255)) +
cast(cast(parsename(ip, 1) as int) as varchar(255))
)
try this solution
DECLARE #IpAdress AS TABLE ( IP VARCHAR(100) )
INSERT #IpAdress
( IP )
VALUES ( '10.001.99.108' ),
( '010.001.099.008' ),
( '080.081.999.008' );
WITH Tally
AS ( SELECT n = 1
UNION ALL
SELECT n + 1
FROM Tally
WHERE n <= 100
),
split
AS ( SELECT i.IP ,
CONVERT(INT, ( CASE WHEN CHARINDEX('.', S.string) > 0
THEN LEFT(S.string,
CHARINDEX('.', S.string)
- 1)
ELSE string
END )) AS part
FROM #IpAdress AS i
INNER JOIN Tally AS T ON SUBSTRING('.' + IP, T.N, 1) = '.'
AND T.N <= LEN(i.IP)
CROSS APPLY ( SELECT String = ( CASE
WHEN T.N = 1
THEN LEFT(i.IP,
CHARINDEX('.',
i.IP) - 1)
ELSE SUBSTRING(i.IP,
T.N, 1000)
END )
) S
)
SELECT DISTINCT
o.ip ,
SUBSTRING(( SELECT '.' + CONVERT(VARCHAR, i.part)
FROM split AS i
WHERE i.ip = o.ip
FOR
XML PATH('')
), 2, 1000) AS newIP
FROM split AS o
output result
ip newIP
010.001.099.008 10.1.99.8
080.081.999.008 80.81.999.8
10.001.99.108 10.1.99.108

saving a query as a view get error

I have this query which runs fine and with no problem!
SELECT [Q].sHost
, LEFT([Q].sDescription, Len([Q].sDescription) - 1) AS [sDescription]
FROM (
SELECT DISTINCT [Q2].sHost
, (
SELECT CONVERT(NVARCHAR(MAX), [Q1].[sDescription]) + N', ' AS [text()]
FROM (
SELECT (
CASE
WHEN (CHARINDEX('\', [sInstance]) = 0)
THEN [sInstance]
ELSE substring([sInstance], 0, CHARINDEX('\', [sInstance]))
END
) AS sHost
, [sDescription]
FROM [db_group].[dbo].[instanceCommentsList]
) AS [Q1]
WHERE ([Q1].sHost = [Q2].sHost)
FOR XML PATH('')
) [sDescription]
FROM (
SELECT (
CASE
WHEN (CHARINDEX('\', [sInstance]) = 0)
THEN [sInstance]
ELSE substring([sInstance], 0, CHARINDEX('\', [sInstance]))
END
) AS sHost
, [sDescription]
FROM [db_group].[dbo].[instanceCommentsList]
) AS [Q2]
) AS [Q]
when I save this query as a view, I get the following error
Error in WHERE clause near '('.
Error in WHERE clause near '='. Unable
to parse query text.
not sure where is the problem!
it's saves any way and it kind of works, I can use it in a simple select query but I get a red line under the name of the view and when I put the mouse over I get this message
The object name is invalid ....
and if I use it in a more complex query it does not work at all
EDIT:----------------------------------
after reading Cannot get FOR XML PATH to work thanks to SelectDistinct's comment
I fixed it but still get one more error!
Error in WHERE clause near '('.
Unable to parse query text.
here is the fixed code:
SELECT [Q].sHost
, LEFT([Q].sDescription, Len([Q].sDescription) - 1) AS [sDescription]
FROM (
SELECT DISTINCT [Q2].sHost
, (
SELECT CONVERT(VARCHAR(MAX), [Q1].[sDescription]) + ', ' AS [text()]
FROM (
SELECT (
CASE
WHEN (CHARINDEX('\', [sInstance]) = 0)
THEN [sInstance]
ELSE substring([sInstance], 0, CHARINDEX('\', [sInstance]))
END
) AS sHost
, [sDescription]
FROM [db_group].[dbo].[instanceCommentsList]
) AS [Q1]
WHERE ([Q1].sHost = [Q2].sHost)
FOR XML PATH(''), type
).value('.', 'varchar(max)') as [sDescription]
FROM (
SELECT (
CASE
WHEN (CHARINDEX('\', [sInstance]) = 0)
THEN [sInstance]
ELSE substring([sInstance], 0, CHARINDEX('\', [sInstance]))
END
) AS sHost
, [sDescription]
FROM [db_group].[dbo].[instanceCommentsList]
) AS [Q2]
) AS [Q]
Try WHERE [Q1].sHost = [Q2].sHost instead of WHERE ([Q1].sHost = [Q2].sHost) .