LEFT used in subquery - sql-server

My problem is quite simple. The following query returns the error:
Mensagem 537, Level 16, state 2, Line 1
Invalid length parameter passed to the LEFT or SUBSTRING function..
Code:
Select
c.name "Nome Conta",
c.code "Código Conta",
sq1.[Nome Conta] Tipo
From
(chart_tag ct inner join chart c on ct.id_chart = c.id_chart) inner join
(Select
c1.name "Nome Conta",
LEFT(c1.code, (charindex('00', c1.code)-1)) "Código Conta"
from chart_tag ct1 inner join chart c1 on ct1.id_chart = c1.id_chart
where ct1.id_tag = 18159 and
c1.id_type_chart = 1942 and
c1.only_accrual = 1 and
c1.code not in ('99', '98')) sq1 on LEFT(c.code, 1) = sq1.[Código Conta]
Where
ct.id_tag = 18159 and
c.id_type_chart = 1942 and
c.only_accrual = 0
order by
c.code
The first inner join returns the following table:
Name
Code
Caixa Geral
1111001
Caixa Departamentos/Operador
1111005
Valores Recebidos a Depositar
1111025
Bancos Conta Movimento
1112001
Bancos Conta Movimento - Vincul
1112005
Bancos Conta Subvenções
1112021
Bancos Conta Doações
1112022
Bancos Conta Contribuições
1112023
Aplicações Financeiras Imediata
1113001
Aplicações Financeiras Vinc.
1113005
Executing the From subquery separately no errors are returned. The following table is generated:
Name
code
ATIVO
1
ATIVO CIRCULANTE
11
CAIXA E EQUIVALENTES
111
CAIXA
1111
BANCOS
1112
APLICAÇÕES FINANCEIRAS
1113
From
Name
code
ATIVO
1000000
ATIVO CIRCULANTE
1100000
CAIXA E EQUIVALENTES
1110000
CAIXA
1111000
BANCOS
1112000
APLICAÇÕES FINANCEIRAS
1113000
Subquery:
Select
c1.name "Nome Conta",
LEFT(c1.code, (charindex('00', c1.code)-1)) "Código Conta"
from
chart_tag ct1 inner join chart c1 on ct1.id_chart = c1.id_chart
where
ct1.id_tag = 18159 and
c1.id_type_chart = 1942 and
c1.only_accrual = 1 and
c1.code not in ('99', '98')
Changing the main logic, which is to remove the '0' to the right of the "code" column, to use replace the query is executed normally and returning the expected result, except for some lines that deviate from the pattern, containing '0' in the middle of the string , and not just right. Because of this, I thought I'd use the current logic, which is returning the error. Knowing this, the LEFT to which the error refers is that of the subquery, however, as already mentioned, as it is an isolated subquery, which does not depend on external factors, I do not understand the reason for this error.

By the way, if you're simply trying to remove the trailing 0 from the number, have you considered:
REPLACE((RTRIM(REPLACE(c1.code, '0', ' ')),' ', '0')
Change all the 0 to spaces, TRIM, and change residual spaces back..
There'll be more ways to skin the cat;
REVERSE(CAST(REVERSE(c1.code) as INT))
(but reverse is generally regarded as a bit nasty/slow) etc..

So, here's how to debug this:
When you don't use LEFT, you don't get the error
The error message complains that the length passed to LEFT is invalid
The problem is with the LEFT function
The length is passed as the second argument
Replace the c1.code with some value:
Select
LEFT('100', (charindex('00', '100')-1)) "Código Conta"
It works, gives you 1
But.. What if the string you seek isn't found in the value?
Select
LEFT('199', (charindex('00', '199')-1)) "Código Conta"
"Invalid length..." error
So what length are we passing? Extract just the length calc:
Select
charindex('00', '199')-1)
--LEFT('199', (charindex('00', '199')-1)) "Código Conta"
It gives -1, and -1 isn't a valid number of chars to remove from the left of something...
So what do you want to do when the needle isn't found in the haystack? Perhaps take the whole string with no substringing? Or maybe adjust the WHERE clause to only allow codes that end with a 00, so the seek always works?
You decide.. This will give you the whole thing if '00' isn't found:
Select
LEFT(
c1.code,
COALESCE(
NULLIF(
charindex('00', c1.code)-1,
-1
),
LEN(c1.Code)
)
) "Código Conta"
If the seek returns -1 meaning "asn't found", NULLIF converts the -1 to null, and COALESCE then converts the null to the LEN of the string, i.e. LEFT returns the whole thing
This will give empty string if the string isn't found; the only difference is use use of 0 in the coalesce - it asks LEFT to give the leftmost 0 chars:
Select
LEFT(
c1.code,
COALESCE(
NULLIF(
charindex('00', c1.code)-1,
-1
),
0
)
) "Código Conta"
Or, as mentioned, use a WHERE c1.code LIKE '%00%' to ensure youre only looking at codes that really do have 00 in them

If it does not have the substring '00'
LEFT(c1.code, (charindex('00', c1.code)-1))
Will give an error since it return 0 and -1 is not valid
This will work:
CASE WHEN charindex('00', c1.code) > 0
THEN LEFT(c1.code, (charindex('00', c1.code)-1))
ELSE c1.code
END
This will also work:
LEFT(c1.code+'00', (charindex('00', c1.code+'00')-1))
I believe the 2nd one will be faster but it may not be based on your data profile so if performance really matters test both.

Related

SQL query gives Incorrect syntax near ')' - use of CAST in INNER JOIN

I get the error Incorrect syntax near ')' from the following SQL query:
SELECT
"GLPOST"."ACCTID",
"GLPOST"."FISCALYR",
"GLPOST"."FISCALPERD",
"GLPOST"."SRCELEDGER",
"GLPOST"."JRNLDATE",
"GLPOST"."BATCHNBR",
"GLPOST"."ENTRYNBR",
"GLPOST"."JNLDTLDESC",
"GLPOST"."JNLDTLREF",
"GLPOST"."TRANSAMT",
"APIBC"."POSTSEQNBR",
"APIBC"."CNTBTCH"
FROM ("MHLDAT"."dbo"."GLPOST" "GLPOST"
INNER JOIN "MHLDAT"."dbo"."GLJEH" "GLJEH"
ON (("GLPOST"."DRILSRCTY"="GLJEH"."DRILSRCTY")
AND ("GLPOST"."DRILLDWNLK"="GLJEH"."DRILLDWNLK")
AND "GLPOST"."DRILAPP"="GLJEH"."DRILAPP")))
INNER JOIN "MHLDAT"."dbo"."APIBC" "APIBC"
ON "APIBC"."POSTSEQNBR" = (CAST ("SUBSTRING" (CAST ("GLPOST"."DRILLDWNLK" AS "CHAR"(18)),3,CAST ("LEFT" (CAST ("GLPOST"."DRILLDWNLK" AS "CHAR"(18)),1) AS "INT" )) AS "INT" ))
WHERE
"GLPOST"."SRCELEDGER"=N'AP' AND "GLPOST"."FISCALYR"=N'2021' AND "GLPOST"."FISCALPERD"=N'01' AND "GLJEH"."ERRBATCH"=0
Any suggestions to resolve please?
SELECT
GLPOST.ACCTID,
GLPOST.FISCALYR,
GLPOST.FISCALPERD,
GLPOST.SRCELEDGER,
GLPOST.JRNLDATE,
GLPOST.BATCHNBR,
GLPOST.ENTRYNBR,
GLPOST.JNLDTLDESC,
GLPOST.JNLDTLREF,
GLPOST.TRANSAMT,
APIBC.POSTSEQNBR,
APIBC.CNTBTCH
FROM MHLDAT.dbo.GLPOST
INNER JOIN MHLDAT.dbo.GLJEH
ON GLPOST.DRILSRCTY=GLJEH.DRILSRCTY
AND GLPOST.DRILLDWNLK=GLJEH.DRILLDWNLK
AND GLPOST.DRILAPP=GLJEH.DRILAPP
INNER JOIN MHLDAT.dbo.APIBC
ON APIBC.POSTSEQNBR = CAST(SUBSTRING(CAST(GLPOST.DRILLDWNLK AS CHAR(18)),
3,
CAST(LEFT(CAST(GLPOST.DRILLDWNLK AS CHAR(18)),
1) AS INT)
) AS INT)
WHERE
GLPOST.SRCELEDGER = N'AP'
AND GLPOST.FISCALYR = N'2021'
AND GLPOST.FISCALPERD = N'01'
AND GLJEH.ERRBATCH = 0
You are using a lot of unnecessary parenthesis and double quoting, remove them (specially on the names of the functions SUBSTRING and LEFT). Remove the aliases (they are unnecessary because you use the same name as the table).
I have aligned your query so you can visually see where starts and ends every subexpression.

Coldfusion - How to parse and segment out data from an email file

I am trying to parse email files that will be coming periodically for data that is contained within. We plan to setup cfmail to get the email within the box within CF Admin to run every minute.
The data within the email consists of name, code name, address, description, etc. and will have consistent labels so we are thinking of performing a loop or find function for each field of data. Would that be a good start?
Here is an example of email data:
INCIDENT # 12345
LONG TERM SYS# C12345
REPORTED: 08:39:34 05/20/19 Nature: FD NEED Address: 12345 N TEST LN
City: Testville
Responding Units: T12
Cross Streets: Intersection of: N Test LN & W TEST LN
Lat= 39.587453 Lon= -86.485021
Comments: This is a test post. Please disregard
Here's a picture of what the data actually looks like:
So we would like to extract the following:
INCIDENT
LONG TERM SYS#
REPORTED
Nature
Address
City
Responding Units
Cross Streets
Comments
Any feedback or suggestions would be greatly appreciated!
Someone posted this but it was apparently deleted. Whoever it was I want to thank you VERY MUCH as it worked perfectly!!!!
Here is the function:
<!---CREATE FUNCTION [tvf-Str-Extract] (#String varchar(max),#Delimiter1
varchar(100),#Delimiter2 varchar(100))
Returns Table
As
Return (
with cte1(N) as (Select 1 From (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1))
N(N)),
cte2(N) as (Select Top (IsNull(DataLength(#String),0)) Row_Number() over (Order By
(Select NULL)) From (Select N=1 From cte1 N1,cte1 N2,cte1 N3,cte1 N4,cte1 N5,cte1 N6) A
),
cte3(N) as (Select 1 Union All Select t.N+DataLength(#Delimiter1) From cte2 t
Where Substring(#String,t.N,DataLength(#Delimiter1)) = #Delimiter1),
cte4(N,L) as (Select S.N,IsNull(NullIf(CharIndex(#Delimiter1,#String,s.N),0)-
S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By N)
,RetPos = N
,RetVal = left(RetVal,charindex(#Delimiter2,RetVal)-1)
From ( Select *,RetVal = Substring(#String, N, L) From cte4 ) A
Where charindex(#Delimiter2,RetVal)>1
)
And here is the CF code that worked:
<cfquery name="body" datasource="#Application.dsn#">
Declare #S varchar(max) ='
INCIDENT 12345
LONG TERM SYS C12345
REPORTED: 08:39:34 05/20/19 Nature: FD NEED Address: 12345 N TEST
LN City: Testville
Responding Units: T12
Cross Streets: Intersection of: N Test LN & W TEST LN
Lat= 39.587453 Lon= -86.485021
Comments: This is a test post. Please disregard
'
Select Incident = ltrim(rtrim(B.RetVal))
,LongTerm = ltrim(rtrim(C.RetVal))
,Reported = ltrim(rtrim(D.RetVal))
,Nature = ltrim(rtrim(E.RetVal))
,Address = ltrim(rtrim(F.RetVal))
,City = ltrim(rtrim(G.RetVal))
,RespUnit = ltrim(rtrim(H.RetVal))
,CrossStr = ltrim(rtrim(I.RetVal))
,Comments = ltrim(rtrim(J.RetVal))
From (values (replace(replace(#S,char(10),''),char(13),' ')) )A(S)
Outer Apply [dbo].[tvf-Str-Extract](S,'INCIDENT' ,'LONG
TERM' ) B
Outer Apply [dbo].[tvf-Str-Extract](S,'LONG TERM SYS'
,'REPORTED' ) C
Outer Apply [dbo].[tvf-Str-Extract](S,'REPORTED:' ,'Nature'
) D
Outer Apply [dbo].[tvf-Str-Extract](S,'Nature:'
,'Address' ) E
Outer Apply [dbo].[tvf-Str-Extract](S,'Address:' ,'City'
) F
Outer Apply [dbo].[tvf-Str-Extract](S,'City:'
,'Responding ') G
Outer Apply [dbo].[tvf-Str-Extract](S,'Responding Units:','Cross'
) H
Outer Apply [dbo].[tvf-Str-Extract](S,'Cross Streets:' ,'Lat'
) I
Outer Apply [dbo].[tvf-Str-Extract](S+'|||','Comments:' ,'|||'
) J
</cfquery>
<cfoutput>
B. #body.Incident#<br>
C. #body.LongTerm#<br>
D. #body.Reported#<br>
SQL tends to have limited string functions, so it isn't the best tool for parsing. If the email content is always in that exact format, you could use either plain string functions or regular expressions to parse it. However, the latter is more flexible.
I suspect the content actually does contain new lines, which would make for simpler parsing. However, if you prefer searching for content in between two labels, regular expressions would do the trick.
Build an array of the label names (only). Loop through the array, grabbing a pair of labels: "current" and "next". Use the two values in a regular expression to extract the text in between them:
label &"\s*[##:=](.*?)"& nextLabel
/* Explanation: */
label - First label name (example: "Incident")
\s* - Zero or more spaces
[##:=] - Any of these characters: pound sign, colon or equal sign
(.*?) - Group of zero or more characters (non-greedy)
nextLabel - Next label (example: "Long Term Sys")
Use reFindNoCase() to get details about the position and length of matched text. Then use those values in conjunction with mid() to extract the text.
Note, newer versions like ColdFusion 2016+ automagically extract the text under a key named MATCH
The newer CF2016+ syntax is slicker, but something along these lines works under CF10:
emailBody = "INCIDENT # 12345 ... etc.... ";
labelArray = ["Incident", "Long Term Sys", "Reported", ..., "Comments" ];
for (pos = 1; pos <= arrayLen(labelArray); pos++) {
// get current and next label
hasNext = pos < arrayLen(labelArray);
currLabel = labelArray[ pos ];
nextLabel = (hasNext ? labelArray[ pos+1 ] : "$");
// extract label and value
matches = reFindNoCase( currLabel &"\s*[##:=](.*?)"& nextLabel, emailBody, 1, true);
if (arrayLen(matches.len) >= 2) {
results[ currLabel ] = mid( emailBody, matches.pos[2], matches.len[2]);
}
}
writeDump( results );
Results:

Subquery returned more than 1 value causing query to fail

I'm stuck with this query. i want to show a field called "Executive" that must say "sin asignar" if there's no match when joining on the table 'prospectousuario', and if it exist it must say the first and the last name of the executive. But it returns the following error:
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an exp
The query is:
select p.IDCANCAP,
p.CEDULA,
p.NOMBRES,
p.APELLIDOS,
p.CELULAR,
p.CASA,
p.CORREO,
p.ESTABLECIMIENTO,
c.DESCRIPCION,
(select case when
p.CEDULA = pu.IDPROSPECTO and pu.IDUSUARIO = u.CEDULA
then u.NOMBRES+' '+u.APELLIDOS else 'Sin asignar'
end from usuario as u, PROSPECTOUSUARIO as pu) as Ejecutivo,
p.FECHA_CREAC
from PROSPECTO p, CANALCAPTACION c
where p.IDCANCAP = c.IDCANAL
Boy this is a guess, based on your SQL and a lot of reading between the lines... but give it a try:
select p.IDCANCAP, p.CEDULA, p.NOMBRES,
p.APELLIDOS, p.CELULAR,p.CASA, p.CORREO,
p.ESTABLECIMIENTO, c.DESCRIPCION, p.FECHA_CREAC,
case when exists
(Select * from PROSPECTOUSUARIO
Where IDPROSPECTO = p.CEDULA)
then u.NOMBRES+' '+ u.APELLIDOS
else 'Sin asignar' end Ejecutivo
from PROSPECTO p
join CANALCAPTACION c on c.IDCANAL = p.IDCANCAP
join usuario u on u.CEDULA = p.CEDULA
Just as the error message says, your one and only subquery
(select case when
p.CEDULA = pu.IDPROSPECTO and pu.IDUSUARIO = u.CEDULA
then u.NOMBRES+' '+u.APELLIDOS else 'Sin asignar'
end from usuario as u, PROSPECTOUSUARIO as pu)
returns more than 1 row, which is not allowed when used as an expression inside the SELECT. In fact, it appears that the u and pu tables are not joined against any other tables in any meaningful way.
To fix, you must join the tables. In fact, you don't even need a subquery for what you're trying to do - move all tables to the FROM section and join them properly, whether inner or outer. Unfortunately, I don't know which fields they should be joined on, so I can't help there without more information or wild guessing.

Getting error: Msg 8134, Level 16, State 1, Line 1

I have reviewed the other replies to this problem, but cannot find the appropriate response to my situation. I am trying to divide "actual hours" by "estimated hours". However, estimated hours can be null (no entry) or zero. Actual hours can be zero. So when I get to the division statement, I can't have estimated hours as null or actual hours as zero. My "coalesce" statements don't appear to be helping. Any advice would be helpful.
SELECT PUV.ProjectName, PUV.[Project ID], PUV.[Project Director], PUV.[Project Owner (Manager)], PUV.[Project Type1], **COALESCE( PUV.[Estimated Hours], 0 ) AS EstHours
,PUV.ProjectStatusDate, PUV.ProjectStartDate, PUV.ProjectBaseline0StartDate, PUV.ProjectActualStartDate, PUV.ProjectStartVariance, PUV.ProjectBaseline0FinishDate
,PUV.ProjectFinishDate, PUV.ProjectFinishVariance, PUV.ProjectActualFinishDate, PUV.ProjectWork, PUV.ProjectBaseline0Work, PUV.ProjectActualWork, PUV.ProjectWorkVariance, PUV.ProjectRemainingWork
,PUV.[Project Phase], PUV.[Hold - Canceled Indicator], CWI.StageName, TB.TB_BASE_NUM, MAX (TB.CREATED_DATE) RecentBaseline
,CASE WHEN PUV.[Estimated Hours] is null then 0 END AS [Estimated%]
,Case When PUV.ProjectActualWork <> 0 THEN cast(COALESCE(PUV.ProjectActualWork,0) as DECIMAL(20,2) )/Cast(COALESCE(PUV.[Estimated Hours],0)as decimal(20,2)) Else 0 End AS [Estimated%]*
--DATEDIFF(day, PUV.ProjectBaseline0FinishDate, PUV.ProjectFinishDate)
FROM MSP_EpmProject_UserView AS PUV
LEFT OUTER JOIN (
SELECT WSI.ProjectUID, WP.PhaseName, WP.PhaseUID, WS.StageName, WS.StageUID
FROM MSP_EpmWorkflowStage AS WS
INNER JOIN MSP_EpmWorkflowPhase AS WP ON WS.PhaseUID = WP.PhaseUID
INNER JOIN MSP_EpmWorkflowStatusInformation AS WSI ON WS.StageUID = WSI.StageUID AND WSI.StageEntryDate IS NOT NULL AND (WSI.StageStatus != 0 AND WSI.StageStatus != 4)
) AS CWI ON PUV.ProjectUID = CWI.ProjectUID
JOIN sps_ppm_IT_Published.dbo.MSP_TASK_BASELINES AS TB ON PUV.ProjectUID = TB.PROJ_UID
WHERE TB.TB_BASE_NUM = 0
GROUP BY PUV.ProjectName, PUV.[Project ID], PUV.[Project Director], PUV.[Project Owner (Manager)], PUV.[Project Type1], PUV.[Estimated Hours]
,PUV.ProjectStatusDate, PUV.ProjectStartDate, PUV.ProjectBaseline0StartDate, PUV.ProjectActualStartDate, PUV.ProjectStartVariance, PUV.ProjectBaseline0FinishDate
,PUV.ProjectFinishDate, PUV.ProjectFinishVariance, PUV.ProjectActualFinishDate, PUV.ProjectWork, PUV.ProjectBaseline0Work, PUV.ProjectActualWork, PUV.ProjectWorkVariance, PUV.ProjectRemainingWork
,PUV.[Project Phase], PUV.[Hold - Canceled Indicator], CWI.StageName, TB.TB_BASE_NUM
ORDER BY PUV.ProjectName
Your title is utterly unrelated to the question and posting a super long, aliased query from your database is not a great way to get help.
Luckily, I was curious enough to see whether "msg-8134-level-16-state-1-line-1" was a secret message from Mars that I checked it out.
Anyway, here's a small example of how to use a CASE statement to avoid division by zero:
SELECT
CASE WHEN example.estimated_hours != 0 AND example.estimated_hours IS NOT NULL
THEN 100 / example.estimated_hours
ELSE 0 END AS ratio
FROM (
SELECT estimated_hours
FROM (
VALUES (0), (1), (NULL), (2), (3))
AS mock_data (estimated_hours))
AS example;
You can just copy/paste that into your SQL terminal and run it to see the output:
ratio
-------
0
100
0
50
33
(5 rows)

Using MAX function for greatest value between a range

Pretty new to tsql functions, and I'm trying to write one that returns a value for the STC_STATUS for the greatest STC_STATUS_DATE where the STC_STATUS_DATE is <= the STC_START_DATE+9. They way I have it now, it returns a null value if there is a STC_STATUS > stc_start_date+9.
SELECT #Result1 = STC_STATUS
FROM STC_STATUSES ss
LEFT OUTER JOIN STUDENT_ACAD_CRED stc ON ss.STUDENT_ACAD_CRED_ID = stc.STUDENT_ACAD_CRED_ID
WHERE ss.STUDENT_ACAD_CRED_ID = ISNULL(#student_acad_cred_id, '0')
AND MAX(ss.STC_STATUS_DATE) <= DATEADD(day,9,stc.STC_START_DATE)
Any help would be greatly appreciated.
EDIT: Sample data per recommendation:
The function returns a null value because pos 1 has a stc_status date that is greater than stc_start_date+9. What I aim to have the function do is return the most recent status date below stc_start_date+9, which would be record 2 in this sample.
you can use TOP 1 to get the correct result
SELECT TOP 1 #Result1 = STC_STATUS
FROM STC_STATUSES ss
LEFT OUTER JOIN STUDENT_ACAD_CRED stc
ON ss.STUDENT_ACAD_CRED_ID = stc.STUDENT_ACAD_CRED_ID
WHERE ss.STUDENT_ACAD_CRED_ID = ISNULL(#student_acad_cred_id, '0')
AND ss.STC_STATUS_DATE <= DATEADD(day,9,stc.STC_START_DATE)
ORDER BY ss.STC_STATUS_DATE desc

Resources