Inserting concatenated values in T-SQL - sql

Ok, this is what I am trying to achieve:
My script below is supposed to gather values across three tables and insert them in TO TABLE B in sequence.
There are two columns that are being affected in TABLE B the INDXLONG AND DDLINE.
In The DDLINE row I am attempting to concatenate values from different fields and to store them as one.
My code is below. Please share any insights:
Declare
#nRowCount int,
#Indxlong int,
#hdrLOCAL char(255),
#CR char(255),
#BLDCHKDT DATETIME,
#BLDCHTIME DATETIME,
#hdrline int,
#1strowline int,
#2ndrowline int,
#3rdrowline int,
#BWP char(255),
#CompAcc char(11),
#BankCode char(11),
#BranchNo char(11),
#PayDate datetime,
#Reference char(11),
#TotaAmt numeric(19,5)
#CoName char(11),
#BeneficiaryAcc char(11),
#BenBankBranchCode char(11),
#Salary numeric (19,5),
#BeneficiaryName char(23),
#TransRef char(23),
#outer_c int
SELECT #CompAcc =DDCOIDEN,
#BankCode =DDIMORIG,
#BranchNo =DDIMDEST,
#Reference =DDDESC10,
#CoName =DDIMORNM
FROM TABLE A
Declare ACH SCROLL CURSOR FOR
SELECT T762.DDINDNAM,
T762.DDTRANUM,
T762.DDACTNUM,
T762.DDAMTDLR,
T756.PAYDATE
FROM STATS.dbo.TABLE C T762
LEFT OUTER JOIN STATS.dbo.TABLE D T756 ON (
T762.INDXLONG = T756.INDXLONG
AND T756.INCLPYMT = 1
)
WHERE (T756.INCLPYMT = 1)
AND (T762.DDAMTDLR <> 0)
FOR READ ONLY;
OPEN ACH;
SET NOCOUNT ON;
FETCH FROM ACH INTO #BeneficiaryName,#BenBankBranchCode,#BeneficiaryAcc,#Salary,#paydate
WHILE ##FETCH_STATUS = 0
BEGIN
Select #TotaAmt =SUM(#Salary)
set #hdrline =1
set #1strowline =2
set #2ndrowline =3
set #3rdrowline =9
SELECT #hdrLOCAL = DDLINE FROM TABLE E WHERE INDXLONG =1
SELECT #CR = DDLINE FROM TABLE E WHERE INDXLONG =2
SELECT #BWP = DDLINE FROM TABLE E WHERE INDXLONG =3
BEGIN
INSERT INTO TABLE B (INDXLONG,DDLINE)
VALUES (1,#hdrLOCAL + ',' + #CR + ',' )
SELECT ##IDENTITY
END
BEGIN
INSERT INTO TABLE B (INDXLONG,DDLINE)
VALUES (2,#CompAcc + #BranchNo +','+ #BWP+ ',' + #PayDate +',' + #Reference + ','+#TotaAmt + ','+ #TransRef)
SELECT ##IDENTITY
END
BEGIN
INSERT INTO TABLE B (INDXLONG,DDLINE)
VALUES (3,#BeneficiaryAcc + ',' + #BenBankBranchCode +','+ #BeneficiaryAcc+ ',' + #Salary +',' + #Reference + ','+#TotaAmt + ','+ #TransRef)
SELECT ##IDENTITY
END
FETCH FROM ACH INTO #BeneficiaryName,#BenBankBranchCode,#BeneficiaryAcc,#Salary,#paydate
END
CLOSE ACH
DEALLOCATE ACH
SET NOCOUNT OFF;
This is the error:
Msg 8152, Level 16, State 14, Line 69
String or binary data would be truncated.
The statement has been terminated.
Msg 241, Level 16, State 1, Line 74
Conversion failed when converting date and/or time from character string.
This is the result I am aiming for:
INDXLONG DDLINE ----------- -----------------------------------------------------------------------
1 101001 029 1403200610A094101 AMEN BANK LOVE
2 123456 111 34567 PPDSALARYPAYT140131140117 11234567
3 63206623 0101962706200 0000062709000319614 ADAMS EVE

Cast your dates into varchars/nvarchars if you are going to concatenate them. For examples, #PayDate should be casted like this: cast(PayDate as varchar(20)). Or if you need the date in a specific format, use Convert.

Related

Using Subqueries to Define Column Alias

I have two tables which I have simplified below for clarity. One stores data values while the other defines the units and type of data. Some tests have one result, others may have more (My actual table has results 1-10):
Table 'Tests':
ID Result1 Result2 TestType(FK to TestTypes Type)
---------- ------------ ----------- -----------
1001 50 29 1
1002 90.9 NULL 2
1003 12.4 NULL 2
1004 20.2 30 1
Table 'TestTypes':
Type TestName Result1Name Result1Unit Result2Name Result2Unit ..........
------- --------- ------------ ----------- ------------ -----------
1 Temp Calib. Temperature F Variance %
2 Clarity Turbidity CU NULL NULL
I would like to use the ResultXName as the column alias when I join the two tables. In other words, if a user wants to see all Type 1 'Temp Calib' tests, the data would be formatted as follows:
Temperature Variance
------------ -----------
50 F 10.1%
20.2 F 4.4%
Or if they look at Type 2, which only uses 1 result and should ignore the NULL:
Turbidity
----------
90.9 CU
12.4 CU
I have had some success in combining the two columns of the tables:
SELECT CONCAT(Result1, ' ', ISNULL(Result1Unit, ''))
FROM Tests
INNER JOIN TestTypes ON Tests.TestType = TestTypes.Type
But I cannot figure out how to use the TestName as the new column alias. This is what I've been trying using a subquery, but it seems subqueries are not allowed in the AS clause:
SELECT CONCAT(Result1, ' ', ISNULL(Result1Unit, '')) AS (SELECT TOP(1) Result1Name FROM TestTypes WHERE Type = 1)
FROM Tests
INNER JOIN TestTypes ON Tests.TestType = TestTypes.Type
Is there a different method I can use? Or do I need to restructure my data to achieve this? I am using MSSQL.
Yes, this can be fully automated by constructing a dynamic SQL string carefully. The key points in this solution and references is listed as follows.
Count the Result variables (section 1.)
Get the new column name of ResultXName by using sp_executesql with the output definition (section 2-1)
Append the clause for the new column (section 2-2)
N.B.1. Although a dynamic table schema is usually considered a bad design, sometimes people are simply ordered to do that. Therefore I do not question the adequacy of this requirement.
N.B.2. Mind the security problem of arbitrary string execution. Additional string filters may be required depending on your use case.
Test Dataset
use [testdb];
GO
if OBJECT_ID('testdb..Tests') is not null
drop table testdb..Tests;
create table [Tests] (
[ID] int,
Result1 float,
Result2 float,
TestType int
)
insert into [Tests]([ID], Result1, Result2, TestType)
values (1001,50,29,1),
(1002,90.9,NULL,2),
(1003,12.4,NULL,2),
(1004,20.2,30,1);
if OBJECT_ID('testdb..TestTypes') is not null
drop table testdb..TestTypes;
create table [TestTypes] (
[Type] int,
TestName varchar(50),
Result1Name varchar(50),
Result1Unit varchar(50),
Result2Name varchar(50),
Result2Unit varchar(50)
)
insert into [TestTypes]([Type], TestName, Result1Name, Result1Unit, Result2Name, Result2Unit)
values (1,'Temp Calib.','Temperature','F','Variance','%'),
(2,'Clarity','Turbidity','CU',NULL,NULL);
--select * from [Tests];
--select * from [TestTypes];
Solution
/* Input Parameter */
declare #type_no int = 1;
/* 1. determine the number of Results */
declare #n int;
-- If there are hundreds of results please use the method as of (2-1)
select #n = LEN(COALESCE(LEFT(Result1Name,1),''))
+ LEN(COALESCE(LEFT(Result2Name,1),''))
FROM [TestTypes]
where [Type] = #type_no;
/* 2. build dynamic query string */
-- cast type number as string
declare #s_type varchar(10) = cast(#type_no as varchar(10));
-- sql query string
declare #sql nvarchar(max) = '';
declare #sql_colname nvarchar(max) = '';
-- loop variables
declare #i int = 1; -- loop index
declare #s varchar(10); -- stringified #i
declare #colname varchar(max); -- new column name
set #sql += '
select
L.[ID]';
-- add columns one by one
while #i <= #n begin
set #s = cast(#i as varchar(10));
-- (2-1) find the new column name
SET #sql_colname = N'select #colname = Result' + #s + 'Name
from [TestTypes]
where [Type] = ' + #s_type;
EXEC SP_EXECUTESQL
#Query = #sql_colname,
#Params = N'#colname varchar(max) OUTPUT',
#colname = #colname OUTPUT;
-- (2-2) sql clause of the new column
set #sql += ',
cast(L.Result' + #s + ' as varchar(10)) + '' '' + R.Result' + #s + 'Unit as [' + #colname + ']'
-- next Result
set #i += 1
end
set #sql += '
into [ans]
from [Tests] as L
inner join [TestTypes] as R
on L.TestType = R.Type
where R.[Type] = ' + #s_type;
/* execute */
print #sql; -- check the query string
if OBJECT_ID('testdb..ans') is not null
drop table testdb..ans;
exec sp_sqlexec #sql;
/* show */
select * from [ans];
Result (type = 1)
| ID | Temperature | Variance |
|------|-------------|----------|
| 1001 | 50 F | 29 % |
| 1004 | 20.2 F | 30 % |
/* the query string */
select
L.[ID],
cast(L.Result1 as varchar(10)) + ' ' + R.Result1Unit as [Temperature],
cast(L.Result2 as varchar(10)) + ' ' + R.Result2Unit as [Variance]
into [ans]
from [Tests] as L
inner join [TestTypes] as R
on L.TestType = R.Type
where R.[Type] = 1
Tested on SQL Server 2017 (linux docker image, latest version) on debian 10

how to merge one row data to one column in sql server

i have one trigger like this:
alter trigger newTrigger
on table2
after insert
as
begin
declare #rowData nvarchar(max)
if exists(select * from inserted)
begin
Set #rowData = (select * from inserted)
end
insert into table1(rowData, date)
values(#rowData, getdate())
end
i want after insert into table2, the inserted record and date insert to table1
but i cant merge the data of row into one column!
------------------table2--------------------
id | name | lname | birth
1 j jj 2000-03-03
-------------------table1 after insert into table2 -----------------
rowData | date
a,j,jj,2000-03-03 2017-05-07
You have to do the conversion and add one by one each column
eg:
Set #rowData = (select cast(col1 as varchar(10)) + cast(col2 as varchar(10)) from inserted)
for your edit with the col names. If the column already is varchar, the only that you have to take care is to avoid the length overflow :
Set #rowData = (select cast((id + ',' + name + ',' + lname + ',' + cast(birth as varchar(12)) ) as varchar(4000)) from inserted)

SQL Server query performance: Nested cursors

I have a stored procedure which is selecting some 1-n relations and related data to the referenced column as XML data.
The purpose is to return a record and it's 1-n relations as ONE record with extra data columns, this is done using adding these related data as XML.
Reference table: (TABLE A)
ID NAME VALUE
---------------------
1 Sepehr 1000
2 Sarah 1001
Related table: (TABLE B)
ID Value FK_Value ORDER TITLE
-------------------------------------
1 A 1000 1 t1
2 B 1000 2 t2
3 C 1000 3 t3
I want to get this output:
ID NAME FK_Value Attribs
-----------------------------------------------------
1 Sepehr 1000 <XML><ID>1</ID><ID>2</ID><ID>3</ID></XML>
2 Sarah 1001 null
Actually I was hoping to create a view to do this, but I couldn't and someone told me its not possible using views.
Finally this is the stored procedure I have written - is this a correct approach or are there any other ways?
DECLARE #T1 table (A_ID int,Attribs XML)
DECLARE db_cursorLegendRowsValues CURSOR FOR
SELECT ID, VALUE
FROM A
OPEN db_cursorLegendRowsValues
FETCH NEXT FROM db_cursorLegendRowsValues INTO #loop_ID, #loop_VALUE
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE db_cursorInternal CURSOR FOR
SELECT TITLE, ORDER
FROM B
WHERE FK_Value = #loop_VALUE
OPEN db_cursorInternal
FETCH NEXT FROM db_cursorInternal INTO #tmpTitle, #ORDER
WHILE ##FETCH_STATUS = 0
BEGIN
SET #querySelect = #querySelect + ', MAX(CASE WHEN order = ' + cast(#ORDER as nvarchar(max)) + ' THEN value END) AS [' +REPLACE (#tmpTitle,' ','_') + '] '
FETCH NEXT FROM db_cursorInternal INTO #tmpTitle, #ORDER
END
CLOSE db_cursorInternal
DEALLOCATE db_cursorInternal
SET #query =
' SELECT ' + cast(#loop_ID as nvarchar(max)) +',(
SELECT A.Value,
'
SET #query = #query + STUFF(#querySelect,1,1,'') + ' FROM A
WHERE [A.Value] = ' + cast(#loop_VALUE as nvarchar(max)) + '
FOR XML RAW (''Item''), root (''Items'') , ELEMENTS XSINIL )'
SET #querySelect = ''
--PRINT(#query)
INSERT into #T1 execute (#query )
FETCH NEXT FROM db_cursorLegendRowsValues INTO #loop_ID, #loop_VALUE
END
CLOSE db_cursorLegendRowsValues
DEALLOCATE db_cursorLegendRowsValues
SELECT * FROM #T1
The whole lot can be condensed to a few lines, no cursors needed at all. This wil be useable in a VIEW:
DECLARE #tblA TABLE(ID INT IDENTITY,NAME VARCHAR(100),VALUE INT);
INSERT INTO #tblA VALUES
('Sepehr',1000)
,('Sarah',1001);
DECLARE #tblB TABLE(ID INT IDENTITY,Value VARCHAR(100),FK_Value INT,[ORDER] INT,TITLE VARCHAR(100));
INSERT INTO #tblB VALUES
('A',1000,1,'t1')
,('B',1000,2,'t2')
,('C',1000,3,'t3');
SELECT a.*
,(SELECT ID FROM #tblB AS b WHERE b.FK_Value=a.VALUE FOR XML PATH(''),ROOT('XML'),TYPE) AS Attribs
FROM #tblA AS a
The result
ID NAME VALUE Attribs
1 Sepehr 1000 <XML><ID>1</ID><ID>2</ID><ID>3</ID></XML>
2 Sarah 1001 NULL
It is possible to do this with just one query - you can use a subquery with your select as follows:
select id
, name
, value as fk_value
, (select id from #table_b b
where a.value = b.fk_value
for xml path (''), root ('xml'))
from #table_a a

Join table using columns which contains other table's id's separated by commas

I have FACULTY table which contain the column with other table's id's separated by commas. I want to join those with respective table.
faculty table:
id | name | course_id | subject_id
a | smith | 2,3 | 1,2
course table:
id | name
1 | bcom
2 | mcom
3 | bba
subject table:
id | name
1 | account
2 | state
3 | economics
I want to get result from these table like..
faculty.id, faculty.name, course.name(using faculty.course_id), subject.name(using faculty.subject_id)
I have tried a lot of queries and also finds from Google but it didn't gave me proper result.
I do not think the performance will be too nice but worths trying. This solution would work in SQL SERVER:
SELECT *
FROM faculty F
JOIN course C
ON ','+F.course_id+',' LIKE '%,'+CONVERT(VARCHAR,C.ID) +',%'
JOIN subject S
ON ','+F.subject_id_id+',' LIKE '%,'+CONVERT(VARCHAR,S.ID) +',%'
Based on Albin Sunnanbo's comment i would also sugget you add some many too many tables:
fcourses
facultyId
courseId
and
fsubjects
facultyId
subjectId
That way you could do a proper join :
SELECT *
FROM faculty F
JOIN fcourses FC
ON F.Id = FC.facultyId
JOIN course C
ON FC.courseId = C.ID
JOIN fsubjects FS
ON F.Id = FS.facultyId
JOIN subject S
ON FS.courseId = S.ID
You can do the following query
select * from faculty F
JOIN course C
on CHARINDEX((','+CAST(c.id as varchar(10))+','), (','+f.courseid+',')) > 0
JOIN subject s
on CHARINDEX((','+CAST(s.id as varchar(10))+','), (','+f.subjectid+',')) > 0
I've done something similar like this:
select f.id, f.lname, f.fname, U.useridlist
from TABLE1 F, TABLE2 U
where ',' || U.useridlist || ',' like '%,' || f.id || ',%'
If you can create a 'string to int table' function, I would look at the following:
Create the function
CREATE FUNCTION [dbo].[udf_ConvertIntListToTable] (#list varchar(MAX))
RETURNS #tbl TABLE (val int) AS
BEGIN
DECLARE #ix int,
#pos int,
#str varchar(MAX),
#num int
SET #pos = 1
SET #ix = 1
WHILE #ix > 0
BEGIN
SET #ix = charindex(',', #list, #pos)
IF #ix > 0
SET #str = substring(#list, #pos, #ix - #pos)
ELSE
SET #str = substring(#list, #pos, len(#list))
SET #str = ltrim(rtrim(#str))
IF #str LIKE '%[0-9]%' AND
(#str NOT LIKE '%[^0-9]%' OR
#str LIKE '[-+]%' AND
substring(#str, 2, len(#str)) NOT LIKE '[-+]%[^0-9]%')
BEGIN
SET #num = convert(int, #str)
INSERT #tbl (val) VALUES(#num)
END
SET #pos = #ix + 1
END
RETURN
END
Then query using CROSS APPLY
declare #FacultyTable table(id int PRIMARY KEY, name nvarchar(50), course_id varchar(50))
declare #CourseTable table(id int PRIMARY KEY, name nvarchar(50))
insert into #FacultyTable values(1, 'Peter Sagal', '11,22')
insert into #FacultyTable values(2, 'Carl Kasell', '22,33')
insert into #CourseTable values(11,'News')
insert into #CourseTable values(22,'News')
insert into #CourseTable values(33,'News')
insert into #CourseTable values(44,'News')
select *
from #FacultyTable f
CROSS APPLY(
SELECT *
FROM #CourseTable c
WHERE
c.id IN (SELECT * FROM dbo.udf_ConvertIntListToTable(f.course_id))
) tCourses

Inserting records from a table with deliminated strings

I have a table structure that contains a identifier column and a column that contains a deliminated string. What I would like to achieve is to insert the deliminated string into a new table as individual records for each of the values in the split deliminated string.
My table structure for the source table is as follows:
CREATE TABLE tablea(personID VARCHAR(8), delimStr VARCHAR(100))
Some sample data:
INSERT INTO tablea (personID, delimStr) VALUES ('A001','Monday, Tuesday')
INSERT INTO tablea (personID, delimStr) VALUES ('A002','Monday, Tuesday, Wednesday')
INSERT INTO tablea (personID, delimStr) VALUES ('A003','Monday')
My destination table is as follows:
CREATE TABLE tableb(personID VARCHAR(8), dayName VARCHAR(10))
I am attempting to create a Stored Procedure to undertake the insert, my SP so far looks like:
CREATE PROCEDURE getTKWorkingDays
#pos integer = 1
, #previous_pos integer = 0
AS
BEGIN
DECLARE #value varchar(50)
, #string varchar(100)
, #ttk varchar(8)
WHILE #pos > 0
BEGIN
SELECT #ttk = personID
, #string = delimStr
FROM dbo.tablea
SET #pos = CHARINDEX(',', #string, #previous_pos + 1)
IF #pos > 0
BEGIN
SET #value = SUBSTRING(#string, #previous_pos + 1, #pos - #previous_pos - 1)
INSERT INTO dbo.tableb ( personID, dayName ) VALUES ( #ttk, #value )
SET #previous_pos = #pos
END
END
IF #previous_pos < LEN(#string)
BEGIN
SET #value = SUBSTRING(#string, #previous_pos + 1, LEN(#string))
INSERT INTO dbo.tableb ( tkinit, dayName ) VALUES ( #ttk, #value )
END
END
The data that was inserted (only 1 records out of the 170 or so in the original table which after spliting the deliminated string should result in about 600 or so records in the new table), was incorrect.
What I am expecting to see using the sample data above is:
personID dayName
A001 Monday
A001 Tuesday
A002 Monday
A002 Tuesday
A002 Wednesday
A003 Monday
Is anyone able to point out any resources or identify where I am going wrong, and how to make this query work?
The Database is MS SQL Server 2000.
I thank you in advance for any assistance you are able to provide.
Matt
Well your SELECT statement which gets the "next" person doesn't have a WHERE clause, so I'm not sure how SQL Server will know to move to the next person. If this is a one-time task, why not use a cursor?
CREATE TABLE #n(n INT PRIMARY KEY);
INSERT #n(n) SELECT TOP 100 number FROM [master].dbo.spt_values
WHERE number > 0 GROUP BY number ORDER BY number;
DECLARE
#PersonID VARCHAR(8), #delimStr VARCHAR(100),
#str VARCHAR(100), #c CHAR(1);
DECLARE c CURSOR LOCAL FORWARD_ONLY STATIC READ_ONLY
FOR SELECT PersonID, delimStr FROM dbo.tablea;
OPEN c;
FETCH NEXT FROM c INTO #PersonID, #delimStr;
SET #c = ',';
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #delimStr = #c + #delimStr + #c;
-- INSERT dbo.tableb(tkinit, [dayName])
SELECT #PersonID, LTRIM(SUBSTRING(#delimStr, n+1, CHARINDEX(#c, #delimStr, n+1)-n-1))
FROM #n AS n
WHERE n.n <= LEN(#delimStr) - 1
AND SUBSTRING(#delimStr, n.n, 1) = #c;
FETCH NEXT FROM c INTO #PersonID, #delimStr;
END
CLOSE c;
DEALLOCATE c;
DROP TABLE #n;
If you create a permanent numbers table (with more than 100 rows, obviously) you can use it for many purposes. You could create a split function that allows you to do the above without a cursor (well, without an explicit cursor). But this would probably work best later, when you finally get off of SQL Server 2000. Newer versions of SQL Server have much more flexible and extensible ways of performing splitting and joining.