Invalid length parameter passed to the LEFT or SUBSTRING function - tsql

I have the following description: 'Sample Product Maker Product Name XYZ - Size' and I would like to only get the value 'Product Name XYZ' from this. If this were just one row I'd have no issue just using SUBSTRING but I have thousands of records and although the initial value Sample Product Maker is the same for all products the Product Name could be different and I don't want anything after the hyphen.
What I have so far has generated the error in the header of this question.
SELECT i.Itemid,
RTRIM(LTRIM(SUBSTRING(i.ShortDescription, 25, (SUBSTRING(i.ShortDescription, 25, CHARINDEX('-', i.ShortDescription, 25)))))) AS ProductDescriptionAbbrev,
CHARINDEX('-', i.ShortDescription, 0) - 25 as charindexpos
FROM t_items i
I am getting 'Argument data type varchar is invalid for argument 3 of substring function'
As you can see, I am getting the value for the last line the sql statement but when I try and plug that into the SUBSTRING function I get various issues.

Chances are good you have rows where the '-' is missing, which is causing your error.
Try this...
SELECT i.Itemid,
SUBSTRING(i.ShortDescription, 22, CHARINDEX('-', i.ShortDescription+'-', 22)) AS ProductDescriptionAbbrev,
FROM t_items i

You could also strip out the Sample Product Maker text and go from there:
SELECT RTRIM(LEFT(
LTRIM(REPLACE(i.ShortDescription, 'Sample Product Maker', '')),
CHARINDEX('-', LTRIM(REPLACE(i.ShortDescription, 'Sample Product Maker',
'' ))) - 1))
AS ShortDescription

Your first call to SUBSTRING specifies a length of SUBSTRING(i.ShortDescription, 25, CHARINDEX('-', i.ShortDescription, 25)).
You might try:
declare #t_items as Table ( ItemId Int Identity, ShortDescription VarChar(100) )
insert into #t_items ( ShortDescription ) values
( 'Sample Product Maker Product Name XYZ - Size' )
declare #SkipLength as Int = Len( 'Sample Product Maker' )
select ItemId,
RTrim( LTrim( Substring( ShortDescription, #SkipLength + 1, CharIndex( '-', ShortDescription, #SkipLength ) - #SkipLength - 1 ) ) ) as ProductDescriptionAbbrev
from #t_items

The problem is that your outer call to SUBSTRING is being passed a character data type from the inner SUBSTRING call in the third parameter.
+--This call does not return an integer type
SELECT i.Itemid, V
RTRIM(LTRIM(SUBSTRING(i.ShortDescription, 25, (SUBSTRING(i.ShortDescription, 25, CHARINDEX('-', i.ShortDescription, 25)))))) AS ProductDescriptionAbbrev,
CHARINDEX('-', i.ShortDescription, 0) - 25 as charindexpos
FROM t_items i
The third parameter must evaluate to the length that you want. Perhaps you meant LEN(SUBSTRING(...))?

Seems like you want something like this (22, not 25):
SELECT i.Itemid,
RTRIM(LTRIM(SUBSTRING(i.ShortDescription, 22, CHARINDEX('-', i.ShortDescription)-22))) AS ProductDescriptionAbbrev,
CHARINDEX('-', i.ShortDescription)-22 as charindexpos
FROM t_items i

You want:
LEFT(i.ShortDescription, isnull(nullif(CHARINDEX('-', i.ShortDescription),0) - 1, 8000))
Note that a good practice is to wrap charindex(...)'s and patindex(...)'s with nullif(...,0), and then handle the null case if desired (sometimes null is the right result, in this case we want all the text so we isnull(...,8000) for the length we want).

Related

Split column into columns using split_part returning empty

I have a column stored as text in postgres 9.6. It's an address and some of the rows have format like BUILD NAME, 40
I saw this answer which looks like what i want, if i run;
select split_part('BUILD NAME, 40', ',', 2) as address_bldgno
It returns; 40 as I want.
When i try;
select
split_part(address, ', ', 1) as address_bldgname,
split_part(address, ', ', 2) as address_bldgno
from table;
It runs but returns empty values
As you mentioned:
...some of the rows have format...
I suspect anything that doesn't contain comma returns empty values.
That is due to the split_part condition does not find a match for the argument.
To illustrate how different address values are split here's some example:
WITH "table" ( address ) AS (
VALUES
( 'BUILD NAME, 40' ), -- first and second have value
( 'BUILD NAME' ), -- only first have value (bldgname)
( '40' ), -- only first have value (bldgname)
( ', 40' ), -- first is empty string, second is value
( 'BUILD NAME,' ), -- first is value, second is empty string
( NULL ), -- both are NULL
( '' ) -- no match found, both are empty strings
)
SELECT split_part( address, ', ', 1 ) as address_bldgname,
split_part( address, ', ', 2 ) as address_bldgno
FROM "table";
-- This is how it looks in result:
address_bldgname | address_bldgno
------------------+----------------
BUILD NAME | 40
BUILD NAME |
40 |
| 40
BUILD NAME, |
|
|
(7 rows)
If you want to set some defaults for NULL and empty string I recommend to read also: this answer
Answer found here;
PostgreSQL 9.3: Split one column into multiple
Should have been;
select somecol
,split_part(address, ', ', 1) as address_bldgname
,split_part(address, ', ', 2) as address_bldgno
from table;
By adding another column to the select I get all the values of that column back, and the split_part() results where they exist.

T-SQL - Creating Graph from data in View

I'm fairly new to T-SQL, Stored Procedures and Microsoft SQL Server Management Studio.
I have created a View in my database called BodyBasics. In this View, there is a column called BackAngle. In the BackAngle column, I list at which angle the users back is bent. The datatype is float and can range from 90 to 180.
Some example values found in this View column are:
173,10786534157, 147,423570266, 170,196359990068, 148,774131860277, 153,439316876929, 147,063469480619, 173,861485242977, 172,1319088368, 145,416983331938, 163,02645970309, 147,65814822779, 146,212510299859, 173,769456580658
The View looks like this:
| ID | Timestamp | RecordingId | BodyNumber | BackAngle |
What I would like to do is SELECT the BackAngle data from the View in chronological order and plot the data into a graph.
The query I have tried is:
GO
DECLARE #BackAngle TABLE(Backangle FLOAT);
INSERT #BackAngle(Backangle) SELECT dbo.ViewBodies.BackAngle FROM dbo.ViewBodies
WHERE dbo.ViewBodies.BackAngle IS NOT NULL
ORDER BY Timestamp;
SELECT geometry::STGeomFromText( 'LINESTRING(' + #BackAngle(Backangle) + ')' );
GO
The errors that I get from this code is:
Msg 102, Level 15, State 1, Line 7
Incorrect syntax near 'Backangle'.
Msg 102, Level 15, State 1, Line 9
Incorrect syntax near '('.
I got the geometry::STGeomFromText syntax from this article:
http://sqlmag.com/t-sql/generating-charts-and-drawings-sql-server-management-studio
Can someone point out what is wrong with my code and whether this is the right way to do this? Is there any alternative?
You don't need a temp table you should make a TEXT STRING with variables to use it in LINESTRING. As a scale line you can use row numbers (1,2,3,4,...)
DECLARE #WKT AS VARCHAR(8000);
SET #WKT =
STUFF(
(SELECT ','
+ CAST( ROW_NUMBER()
OVER (ORDER BY [timestamp]) AS VARCHAR(100))
+ ' ' + CAST( BackAngle AS VARCHAR(30) )
FROM ViewBodies
WHERE BackAngle IS NOT NULL
ORDER BY [timestamp]
FOR XML PATH('')), 1, 1, '');
SELECT geometry::STGeomFromText( 'LINESTRING(' + #WKT + ')', 0 );

Using IndexOf and/Or Substring to parse data into separate columns

I am working on migrating data from one database to another for a hospital. In the old database, the doctor's specialty IDs are all in one column (swvar_specialties), each separated by commas. In the new database, each specialty ID will have it's own column (example: Specialty1_PrimaryID, Specialty2_PrimaryID, Specialty3_PrimaryID, etc). I am trying to export the data out of the old database and separate these into these separate columns. I know I can use indexof and substring to do this - I just need help with the syntax.
So this query:
Select swvar_specialties as Specialty1_PrimaryID
From PhysDirectory
might return results similar to 39,52,16. I need this query to display Specialty1_PrimaryID = 39, Specialty2_PrimaryID = 52, and Specialty3_PrimaryID = 16 in the results. Below is my query so far. I will eventually have a join to pull the specialty names from the specialties table. I just need to get this worked out first.
Select pd.ref as PrimaryID, pd.swvar_name_first as FirstName, pd.swvar_name_middle as MiddleName,
pd.swvar_name_last as LastName, pd.swvar_name_suffix + ' ' + pd.swvar_name_degree as NameSuffix,
pd.swvar_birthdate as DateOfBirth,pd.swvar_notes as AdditionalInformation, 'images/' + '' + pd.swvar_photo as ImageURL,
pd.swvar_philosophy as PhilosophyOfCare, pd.swvar_gender as Gender, pd.swvar_specialties as Specialty1_PrimaryID, pd.swvar_languages as Language1_Name
From PhysDirectory as pd
The article Split function equivalent in T-SQL? provides some details on how to use a split function to split a comma-delimited string.
By modifying the table-valued function in presented in this article to provide an identity column we can target a specific row such as Specialty1_PrimaryID:
/*
Splits string into parts delimitered with specified character.
*/
CREATE FUNCTION [dbo].[SDF_SplitString]
(
#sString nvarchar(2048),
#cDelimiter nchar(1)
)
RETURNS #tParts TABLE (id bigint IDENTITY, part nvarchar(2048) )
AS
BEGIN
if #sString is null return
declare #iStart int,
#iPos int
if substring( #sString, 1, 1 ) = #cDelimiter
begin
set #iStart = 2
insert into #tParts
values( null )
end
else
set #iStart = 1
while 1=1
begin
set #iPos = charindex( #cDelimiter, #sString, #iStart )
if #iPos = 0
set #iPos = len( #sString )+1
if #iPos - #iStart > 0
insert into #tParts
values ( substring( #sString, #iStart, #iPos-#iStart ))
else
insert into #tParts
values( null )
set #iStart = #iPos+1
if #iStart > len( #sString )
break
end
RETURN
END
Your query can the utilise this split function as follows:
Select
pd.ref as PrimaryID,
pd.swvar_name_first as FirstName,
pd.swvar_name_middle as MiddleName,
pd.swvar_name_last as LastName,
pd.swvar_name_suffix + ' ' + pd.swvar_name_degree as LastName,
pd.swvar_birthdate as DateOfBirth,pd.swvar_notes as AdditionalInformation,
'images/' + '' + pd.swvar_photo as ImageURL,
pd.swvar_philosophy as PhilosophyOfCare, pd.swvar_gender as Gender,
(Select part from SDF_SplitString(pd.swvar_specialties, ',') where id=1) as Specialty1_PrimaryID,
(Select part from SDF_SplitString(pd.swvar_specialties, ',') where id=2) as Specialty2_PrimaryID,
pd.swvar_languages as Language1_Name
From PhysDirectory as pd

TSQL - CONCAT_NULL_YIELDS_NULL ON Not Returning Null?

I have had a look around and seem to have come across a strange issue with SQL Server 2008 R2.
I understand that with CONCAT_NULL_YIELDS_NULL = ON means that the following will always resolve to NULL
SELECT NULL + 'My String'
I'm happy with that, however when using this in conjunction with COALESCE() it doesn’t appear to be working on my database.
Consider the following query where MyString is VARCHAR(2000)
SELECT COALESCE(MyString + ', ', '') FROM MyTableOfValues
Now in my query, when MyString IS NULL it returns an empty (NOT NULL) string. I can see this in the query results window.
However unusually enough, when running this in conjunction with an INSERT it fails to recognise the CONCAT_NULL_YIELDS_NULL instead, inserting a blank ‘, ‘.
Query is as follows for insert.
CONCAT_NULL_YIELDS_NULL ON
INSERT INTO Mytable(StringValue)
SELECT COALESCE(MyString + ', ', '')
FROM MyTableOfValues
Further to this I have also checked the database and CONCAT_NULL_YIELDS_NULL = TRUE…
Use NULLIF(MyString, '') instead of just MyString:
SELECT COALESCE(NULLIF(MyString, '') + ', ', '') FROM MyTableOfValues
Coalesce returns the first nonnull expression among its arguments.
You're getting a ', ' because it's the first nonnull expression in your coalesce call.
http://msdn.microsoft.com/en-us/library/ms190349.aspx
From some of the answers provided I was able to assertain a more in depth understanding of COALESCE().
The reason the above query did not fully work was because although I was checking for nulls, and empty string ('') is not considered null. Therefore although the above query worked, I should have checked for empty strings in my table first.
e.g.
SELECT COALESCE(FirstName + ', ', '') + Surname
FROM
(
SELECT 'Joe' AS Firstname, 'Bloggs' AS Surname UNION ALL
SELECT NULL, 'Jones' UNION ALL
SELECT '', 'Jones' UNION ALL
SELECT 'Bob', 'Tielly'
) AS [MyTable]
Will return
FullName
-----------
Joe, Bloggs
Jones
, Jones
Bob, Tielly
Now row 3 has returned a "," character which I was not originally expecting due to a Blank but NOT NULL value.
The following code now works as expected as it checks for blank values. It works, but it looks like I took the long way around. There may be a better way.
-- Ammended Query
SELECT COALESCE(REPLACE(FirstName, Firstname , Firstname + ', '), '') + Surname AS FullName0
FROM
(
SELECT 'Joe' AS Firstname, 'Bloggs' AS Surname UNION ALL
SELECT NULL, 'Jones' UNION ALL
SELECT '', 'Jones' UNION ALL
SELECT 'Bob', 'Tielly'
) AS [MyTable]

Dynamic pivot - how to obtain column titles parametrically?

I wish to write a Query for SAP B1 (t-sql) that will list all Income and Expenses Items by total and month by month.
I have successfully written a Query using PIVOT, but I do not want the column headings to be hardcoded like: Jan-11, Feb-11, Mar-11 ... Dec-11.
Rather I want the column headings to be parametrically generated, so that if I input:
--------------------------------------
Query - Selection Criteria
--------------------------------------
Posting Date greater or equal 01.09.10
Posting Date smaller or equal 31.08.11
[OK] [Cancel]
the Query will generate the following columns:
Sep-10, Oct-10, Nov-10, ..... Aug-11
I guess DYNAMIC PIVOT can do the trick.
So, I modified one SQL obtained from another forum to suit my purpose, but it does not work. The error message I get is Incorrect Syntax near 20100901.
Could anybody help me locate my error?
Note: In SAP B1, '[%1]' is an input variable
Here's my query:
/*Section 1*/
DECLARE #listCol VARCHAR(2000)
DECLARE #query VARCHAR(4000)
-------------------------------------
/*Section 2*/
SELECT #listCol =
STUFF(
( SELECT DISTINCT '],[' + CONVERT(VARCHAR, MONTH(T0.RefDate), 102)
FROM JDT1
FOR XML PATH(''))
, 1, 2, '') + ']'
------------------------------------
/*Section 3*/
SET #query = '
SELECT * FROM
(
SELECT
T0.Account,
T1.GroupMask,
T1.AcctName,
MONTH(T0.RefDate) as [Month],
(T0.Debit - T0.Credit) as [Amount]
FROM dbo.JDT1 T0
JOIN dbo.OACT T1 ON T0.Account = T1.AcctCode
WHERE
T1.GroupMask IN (4,5,6,7) AND
T0.[Refdate] >= '[%1]' AND
T0.[Refdate] <= '[%2]'
) S
PIVOT
(
Sum(Amount)
FOR [Month] IN ('+#listCol+')
) AS pvt
'
--------------------------------------------
/*Section 4*/
EXECUTE (#query)
I don't know SAP, but a couple of things spring to mind:
It looks like you want #listCol to contain a collection of numbers within square brackets, for example [07],[08],[09].... However, your code appears not to put a [ at the start of this string.
Try replacing the lines
T0.[Refdate] >= '[%1]' AND
T0.[Refdate] <= '[%2]'
with
T0.[Refdate] >= ''[%1]'' AND
T0.[Refdate] <= ''[%2]''
(I also added a space before the AND in the first of these two lines while I was editing your question.)