Concate Primary Keys in SQL - sql

I want to concate Primary Keys of multiple tables in SQL directly. I used below query to concate three primary keys with a hyphen between them but the SQL skipped the hyphen and sum up the primary keys and result in a single value.
SELECT CID + '-' + RID + '-'+ CGID As [IdCombination] ...
where CID , RID and CGID are the Primary Keys of three SQL Tables.
How it skipped the string part in query ?
Any help would be highly appreciated.
Updated
For Example : The Values of CID , RID and CGID are 3 , 4, 3 respectively. It should be 3-4-3 but the result is 10.

What is happening? Remember that + means both addition and string concatenation. It so happens that - can be interpreted as a number (like -0), so SQL Server prefers to interpret the + as addition.
Normally, when you do this type of operation, the separation character cannot be interpreted as a number, and you just get an error. I am amused that you don't get an error in this case.
One method is to explicitly cast the values as strings:
SELECT CAST(CID as VARCHAR(255)) + '-' + CAST(RID + as VARCHAR(255)) '-'+ CAST(CGID as VARCHAR(255)) As [IdCombination]
In SQL Server 2012+, you can do this more simply using CONCAT():
SELECT CONCAT(CID, '-', RID, '-', 'CGID) As [IdCombination]
CONCAT() knows that everything should be a string.

Try this
SELECT 5 + '-' + 8
The output is 13. I must admit, that I did not expect this...
And now try this
SELECT CAST('-' AS INT)
The result is 0. As your select starts with an INT, SQL Server tries to do a summa of int values. As the single hyphen is casteable to int implicitly, this returns the summa of your values...
The solution, as pointed out by others is either a cast of your column values to a string type or the usage of CONCAT

I would need to see output, but I am assuming, some ID is stored as int and it is being counted, so you should use
SELECT Cast(CID as Varchar(50)) + '-' + Cast(RID as Varchar(50)) + '-'+ Cast(CGID as Varchar(50)) As [IdCombination]

Related

TSQL CTE error ''Types don't match between the anchor and the recursive part"

Would someone help me understand the details of the error below..? This is for SQL Server 2008.
I did fix it myself, and found many search hits which show the same fix, but none explain WHY this happens in a CTE.
Types don't match between the anchor and the recursive part in column "txt" of recursive query "CTE".
Here is an example where I resolved the issue with CAST, but why does it work?
WITH CTE(n, txt) AS
(
--SELECT 1, '1' --This does not work.
--SELECT 1, CAST('1' AS varchar) --This does not work.
--SELECT 1, CAST('1' AS varchar(1000)) --This does not work.
SELECT
1,
CAST('1' AS varchar(max)) --This works. Why?
UNION ALL
SELECT
n+1,
txt + ', ' + CAST(n+1 AS varchar) --Why is (max) NOT needed?
FROM
CTE
WHERE
n < 10
)
SELECT *
FROM CTE
I assume there are default variable types at play which I do not understand, such as:
what is the type for something like SELECT 'Hello world! ?
what is the type for the string concatenation operator SELECT 'A' + 'B' ?
what is the type for math such as SELECT n+1 ?
The info you want is all in the documentation:
When concatenating two char, varchar, binary, or varbinary expressions, the length of the resulting expression is the sum of the lengths of the two source expressions, up to 8,000 bytes.
snip ...
When comparing two expressions of the same data type but different lengths by using UNION, EXCEPT, or INTERSECT, the resulting length is the longer of the two expressions.
The precision and scale of the numeric data types besides decimal are fixed. When an arithmetic operator has two expressions of the same type, the result has the same data type with the precision and scale defined for that type.
However, a recursive CTE is not the same as a normal UNION ALL:
The data type of a column in the recursive member must be the same as the data type of the corresponding column in the anchor member.
So in answer to your questions:
'Hello world!' has the data type varchar(12) by default.
'A' + 'B' has the data type varchar(2) because that is the sum length of the two data types being summed (the actual value is not relevant).
n+1 is still an int
In a recursive CTE, the data type must match exactly, so '1' is a varchar(1). If you specify varchar without a length in a CAST then you get varchar(30), so txt + ', ' + CAST(n+1 AS varchar) is varchar(33).
When you cast the anchor part to varchar(max), that automatically means the recursive part will be varchar(max) also. You don't need to cast to max, you could also cast the recursive part directly to varchar(30) for example:
WITH CTE(n, txt) AS
(
--SELECT 1, '1' --This does not work.
SELECT 1, CAST('1' AS varchar(30)) --This does work.
--SELECT 1, CAST('1' AS varchar(1000)) --This does not work.
UNION ALL
SELECT
n+1,
CAST(CONCAT(txt, ', ', n+1) AS varchar(30))
FROM
CTE
WHERE
n < 10
)
SELECT *
FROM CTE
db<>fiddle
If you place the query into a string then you can get the result set data types like with the query :
DECLARE #query nvarchar(max) = 'SELECT * FROM table_name';
EXEC sp_describe_first_result_set #query, NULL, 0;

SQL Server reducing the length of the string to 8000 characters

I am trying to insert data in a table with column datatype as NTEXT. Ideally it should store more than 8000 characters, but the in my case it is reducing it to 8000 characters.
I am making the Insert Query at runtime in Procedure. Below is the sample query that procedure is making.
INSERT INTO TMPRESULTS SELECT ('A' + ',' + 'B' + ',' + 'C')
A,B,C, etc. are sample data and actual data will be identified at runtime with actual content crossing 8000 characters. Also the variable used to store the value are defined as 'NVARCHAR(MAX)'
However, when I try following query it does insert more than 8000 character in the table
INSERT INTO TMPRESULTS SELECT ('ABCdddd................')
I presume while I am trying to concat the data with '+' sign, sql server is reducing the length to 8000. I can't use CONCAT as data will be more than 256 columns/arguments.
Any idea, why it is doing so? Also, if someone can help with some alternate solution as I will have to make insert query at runtime.
This is documented in + (String Concatenation) (Transact-SQL) - Remarks:
If the result of the concatenation of strings exceeds the limit of
8,000 bytes, the result is truncated. However, if at least one of the
strings concatenated is a large value type, truncation does not occur.
For a varchar 8,000 bytes would be 8,000 characters, and for a nvarchar 4,000.
All your literal strings in the query INSERT INTO TMPRESULTS SELECT ('A' + ',' + 'B' + ',' + 'C') are non large value types (In fact, they are all a varchar(1)). If you CONVERT/CAST one of them to a varchar(MAX) this would solve the problem:
INSERT INTO TMPRESULTS
SELECT (CONVERT(varchar(MAX),'A') + ',' + 'B' + ',' + 'C');
if you want an nvarchar, make sure you declare your literal strings as a nvarchar too:
INSERT INTO TMPRESULTS
SELECT (CONVERT(nvarchar(MAX),N'A') + N',' + N'B' + N',' + N'C');
In SQL Server 2017 onwards, there is CONCAT_WS function to perform concatenation easily. You can also read about CONCAT
So, instead of this:
INSERT INTO TMPRESULTS SELECT ('A' + ',' + 'B' + ',' + 'C')
We can have below:
INSERT INTO TMPRESULTS SELECT CONCAT_WS(CAST(N',' AS NVARCHAR(MAX)),'A','B','C'))
I have put sample below from SQL Server 2017 for reference:
CREATE TABLE #tempValue(BigValue NVARCHAR(MAX))
INSERT INTO #tempValue
SELECT CONCAT_WS(CAST(N',' AS NVARCHAR(MAX)),REPLICATE('A',4000),REPLICATE('B',4000),REPLICATE('C',4000))
SELECT LEN(BigValue) FROM #tempValue -- 12002
Also, CONCAT_WS is better for below reasons:
If CONCAT_WS receives arguments with all NULL values, it will return
an empty string of type varchar(1).
CONCAT_WS ignores null values during concatenation, and does not add
the separator between null values.

Understanding the Syntax for COALESCE

I am trying to understand the below syntax. Can I get some help with this.
DECLARE #StringList VARCHAR(2500);
SELECT COALESCE(#StringList + ',','') + CAST(apID as VARCHAR) AS ApIdList FROM testTable
As a result you will get all apID from testTable in VARCHAR
COALESCE
checks if first parameter is NULL then the second parameter will returned. In this line #StringList is always equals NULL
COALESCE(#StringList + ',','')
So, NULL + ',' = NULL and you will get empty string ('')
Then empty string + CAST(apID as VARCHAR) and you will get apID as VARCHAR
Coalesce returns the first non-null element provided in the list supplied. See - https://msdn.microsoft.com/en-us/library/ms190349.aspx.
In your case, if #StringList is not null, then it's contents will be prepended with a comma to appID for each row in testTable.
Your code is returning ApID as a string for all rows in the table. Why? Because #StringList is NULL so the first expression evaluates to '' and the second to a string representation of the ApId in some row in the table.
I caution you about the conversion to VARCHAR with no length. Don't do this! The default length varies by context, and you can introduce very hard-to-debug errors without a length.
A related expression is more common, I think:
SELECT #StringList = COALESCE(#StringList + ',', '') + CAST(apID as VARCHAR(8000)) AS ApIdList
FROM testTable;
This does string concatenation, so all the values of apID are concatenated together in a comma-delimited string.
What this is doing is looping on the result set to assign the variable. This type of assignment of a variable across multiple rows is discouraged. I don't think that SQL Server guarantees that it actually works (i.e. is a documented feature), but it appears to work in practice across all versions.

How to use literal backslash in SQL query statement?

EDIT2: Sorry everyone, problem solved. I WAS trying to concatenate a number with my string. I forgot that I simplified my statement when I posted here, thinking my problem had to do with the backslash rather than type compatibility. You guys are right and I was being an idiot. Thanks!
I'm using Access 2013 where my query pulls its data from a SQL 10.0 server (using pass-through).
I am trying to use the backslash in my SQL query like below (***edit: tbltask.jobnum is a string in my database):
SELECT [tblEstimator].[Name] + '\\20' + [tbltask].[JobNum] + ' JOBS\\' AS JobMidFilePath
But when I run the query, I get the error:
Conversion failed when converting the varchar value '\\20' to data type smallint. (#245)
I have no idea what this means or why it's trying to convert anything to smallint.
To replicate your issue, we can write a query something like this:
declare #name varchar(50) = 'Test',
#JobNum smallint = 12
select #name + '\\20' + #JobNum + ' JOBS\\';
This will return the same error:
Conversion failed when converting the varchar value 'Test\20' to data
type smallint.
But if you convert the smallint or the number field to a string like this, the error should go away:
declare #name varchar(50) = 'Test',
#JobNum smallint = 12
select #name + '\\20' + cast(#JobNum as varchar(10)) + ' JOBS\\'
Your query should look something like this:
SELECT [tblEstimator].[Name] + '\\20' + cast([tbltask].[JobNum] as varchar(10))
+ ' JOBS\\' AS JobMidFilePath
I am not sure the equivalent of MS-ACESS on this, but that should resolve your issue.
/*You can copy/paste directly into SQL, comments will be commented out ini SQL syntax
I would create a temp table to achieve this. */
USE whateverDB
GO
CREATE TABLE #toSelectFrom (
Name VARCHAR (100)
,JobNum TINYINT
/*look at the schema, it'll tell you what the data type is in SQL tables, in the column folder [see image] (http://i.ytimg.com/vi/MKPtdjq3MzA/maxresdefault.jpg) - copy verbatim to your temp table columns */
,slashPathNumber VARCHAR (25)
,slashPathJobs VARCHAR (50)
)
INSERT INTO #toSelectFrom (Name, slashPathNumber, JobNum, slashPathJobs)
SELECT [tblEstimator].[Name], '\\\\20', [tbltask].[JobNum], ' JOBS\\' AS JobMidFilePath /*** FROM yourTableToSelectFrom -- you NEED THIS FOR VALID SQL! ***/
--Then just:
SELECT * FROM #toSelectFrom;
-- OR Alternatively:
SELECT Name + slashPathNumber + JobNum + slashPathJobs AS JobMidFilePath FROM #toSelectFrom;
/*
If you use it a lot, just write the above select statement(s) into a view to select from more often. ;)
*/
First off, you can try to cast/convert your int as your output should be string. If the single quote doesn't work, you can try using double qoute (")
SELECT [tblEstimator].[Name] + "\\20" + CAST([tbltask].[JobNum] as varchar(100)) + " JOBS\\" AS JobMidFilePath

Concatenate sql values to a variable

On a SQL Server 2008 I'm trying to get a comma separated list of all selected values into a variable.
SELECT field
FROM table
returns:
+-------+
| field |
+-------+
| foo |
+-------+
| bar |
+-------+
I'd like to get:
"foo, bar, "
I tried:
DECLARE #foo NVARCHAR(MAX)
SET #foo = ''
SELECT #foo = #foo + field + ','
FROM TABLE
PRINT #foo
Which returns nothing. What am I doing wrong?
You'll need to change NULLs
SELECT #foo = #foo + ISNULL(field + ',', '')
FROM TABLE
or remove them
SELECT #foo = #foo + field + ','
FROM TABLE
WHERE field IS NOT NULL
That happens if you have even a SINGLE field in the table that is NULL. In SQL Server, NULL + <any> = NULL. Either omit them
SELECT #foo = #foo + field + ','
FROM TABLE
WHERE field is not null
Or work around them
SELECT #foo = #foo + isnull(field + ',', '')
FROM TABLE
You can write the whole thing without the leading SET statement which is more common. This query below returns "foo,bar" with no trailing comma
DECLARE #foo NVARCHAR(MAX)
SELECT #foo = isnull(#foo + ',', '') + field
FROM TABLE
WHERE field is not null
PRINT #foo
Don't forget to use LTRIM and RTRIM around #foo (when data type is char/varchar) in the concatenation other it will not give expected results in SQL 2008 R2.
As per the comment Lukasz Szozda made on one of the answers here, you should not use your indicated method to aggregate string values in SQL Server, as this is not supported functionality. While this tends to work when no order clause is used (and even if no exception to this tendency has ever been documented), Microsoft does not guarantee that this will work, and there's always a chance it could stop working in the future. SQL is a declarative language; you cannot assume that behaviour that is not explicitly defined as being the correct behaviour for interpreting a given statement will continue working.
Instead, see the examples below, or see this page for a review of valid ways to achieve the same result, and their respective performance: Optimal way to concatenate/aggregate strings
Doing it in a valid way, whichever way you end up using, still has the same considerations as in the other answers here. You either need to exclude NULL values from your result set or be explicit about how you want them to be added to the resulting string.
Further, you should probably use some kind of explicit ordering so that this code is deterministic - it can cause all sorts of problems down the line if code like this can produce a different result when running on the same data, which may happen without an explicit ordering specified.
--Null values treated as empty strings
SET #Foo =
STUFF /*Stuff is used to remove the seperator from the start of the string*/
( (SELECT N','/*separator*/ + ISNULL(RTRIM(t.Field), '' /*Use an emptry string in the place of NULL values*/) /*Thing to List*/
FROM TABLE t
ORDER BY t.SomeUniqueColumn ASC /*Make the query deterministic*/
FOR XML PATH, TYPE).value(N'.[1]',N'varchar(max)')
,1
,1 /*Length of separator*/
,N'');
--Null values excluded from result
SET #Foo =
STUFF /*Stuff is used to remove the seperator from the start of the string*/
( (SELECT N','/*separator*/ + RTRIM(t.Field) /*Thing to List*/
FROM TABLE t
WHERE t.Field IS NOT NULL
ORDER BY t.SomeUniqueColumn ASC /*Make the query deterministic*/
FOR XML PATH, TYPE).value(N'.[1]',N'varchar(max)')
,1
,1 /*Length of separator*/
,N'');