MicrosoftSQL: How to create a table inside a Trigger and insert values - sql-server

im trying to create a trigger on a table with the follow characteristics
Whenever and UPDATE is used on Project_Ypalliloi (table name) i would like to Create another Table (for instance deleted_Ypalliloi)
I would like to take the deleted line, and insert it into the new Table
this is my table:
create table Project_Ypalliloi
(
arithmos_taut int primary key not null,
onoma varchar(20)not null,
eponymo varchar(20)not null,
imerominia_proslipsis date not null,
imerominia_gennisis date not null,
misthos float
)
this is my trigger:
CREATE TRIGGER deleteTrigger ON Project_Ypalliloi FOR DELETE AS --errorline1
DECLARE #arithmos_taut int
DECLARE #onoma varchar(20)
DECLARE #eponymo varchar(20)
DECLARE #imerominia_proslipsis date
DECLARE #imerominia_gennisis date
DECLARE #misthos float
DECLARE #getnamesCursor CURSOR
SET #getnamesCursor = CURSOR FOR
SELECT arithmos_taut,onoma,eponymo,imerominia_proslipsis,imerominia_gennisis,misthos FROM Project_Ypalliloi --where How can i get the deleted line?(under what condition?)
OPEN #getnamesCursor
FETCH NEXT FROM #getnamesCursor INTO #arithmos_taut,#onoma,#eponymo,#imerominia_proslipsis,#imerominia_gennisis,#misthos
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO deleted_Ypalliloi Values '('+rtrim(#arithmos_taut) + ',' + rtrim(#onoma) + ',' + rtrim(#eponymo) + ' ,' + rtrim(#imerominia_proslipsis) + ',' + rtrim(#imerominia_gennisis) + ', ' + rtrim(#misthos)+')'
--FETCH NEXT FROM #getnamesCursor INTO #c_name,#c_surname
END --errorline 2
CLOSE #getnamesCursor
DEALLOCATE #getnamesCursor
My trigger code is in the Query,and i get an error:"Incorrect Syndax at errorline 1 and errorline 2
Thanks a lot for your help

USE "FROM DELETED"
CREATE TRIGGER deleteTrigger ON Project_Ypalliloi AFTER DELETE
AS
BEGIN
DECLARE #arithmos_taut int;
DECLARE #onoma varchar(20);
DECLARE #eponymo varchar(20);
DECLARE #imerominia_proslipsis date;
DECLARE #imerominia_gennisis date;
DECLARE #misthos float;
SELECT #arithmos_taut = arithmos_taut,
#onoma = onoma, #eponymo = eponymo,
#imerominia_proslipsis = imerominia_proslipsis,
#imerominia_gennisis = imerominia_gennisis,
#misthos = misthos FROM DELETED;
INSERT INTO deleted_Ypalliloi
VALUES(#arithmos_taut, #onoma, #eponymo, #imerominia_proslipsis, #imerominia_gennisis, #misthos);
END
For two or more deleted rows
INSERT INTO deleted_Ypalliloi SELECT * FROM DELETED;

Related

combine #sql query with temp table in SQL [duplicate]

In my stored procedure I declared two table variables on top of my procedure. Now I am trying to use that table variable within a dynamic sql statement but I get this error at the time of execution of that procedure. I am using Sql Server 2008.
This is how my query looks like,
set #col_name = 'Assoc_Item_'
+ Convert(nvarchar(2), #curr_row1);
set #sqlstat = 'update #RelPro set '
+ #col_name
+ ' = (Select relsku From #TSku Where tid = '
+ Convert(nvarchar(2), #curr_row1) + ') Where RowID = '
+ Convert(nvarchar(2), #curr_row);
Exec(#sqlstat);
And I get the following errors,
Must declare the table variable "#RelPro".
Must declare the table variable "#TSku".
I have tried to take the table outside of the string block of dynamic query but to no avail.
On SQL Server 2008+ it is possible to use Table Valued Parameters to pass in a table variable to a dynamic SQL statement as long as you don't need to update the values in the table itself.
So from the code you posted you could use this approach for #TSku but not for #RelPro
Example syntax below.
CREATE TYPE MyTable AS TABLE
(
Foo int,
Bar int
);
GO
DECLARE #T AS MyTable;
INSERT INTO #T VALUES (1,2), (2,3)
SELECT *,
sys.fn_PhysLocFormatter(%%physloc%%) AS [physloc]
FROM #T
EXEC sp_executesql
N'SELECT *,
sys.fn_PhysLocFormatter(%%physloc%%) AS [physloc]
FROM #T',
N'#T MyTable READONLY',
#T=#T
The physloc column is included just to demonstrate that the table variable referenced in the child scope is definitely the same one as the outer scope rather than a copy.
Your EXEC executes in a different context, therefore it is not aware of any variables that have been declared in your original context. You should be able to use a temp table instead of a table variable as shown in the simple demo below.
create table #t (id int)
declare #value nchar(1)
set #value = N'1'
declare #sql nvarchar(max)
set #sql = N'insert into #t (id) values (' + #value + N')'
exec (#sql)
select * from #t
drop table #t
You don't have to use dynamic SQL
update
R
set
Assoc_Item_1 = CASE WHEN #curr_row = 1 THEN foo.relsku ELSE Assoc_Item_1 END,
Assoc_Item_2 = CASE WHEN #curr_row = 2 THEN foo.relsku ELSE Assoc_Item_2 END,
Assoc_Item_3 = CASE WHEN #curr_row = 3 THEN foo.relsku ELSE Assoc_Item_3 END,
Assoc_Item_4 = CASE WHEN #curr_row = 4 THEN foo.relsku ELSE Assoc_Item_4 END,
Assoc_Item_5 = CASE WHEN #curr_row = 5 THEN foo.relsku ELSE Assoc_Item_5 END,
...
from
(Select relsku From #TSku Where tid = #curr_row1) foo
CROSS JOIN
#RelPro R
Where
R.RowID = #curr_row;
You can't do this because the table variables are out of scope.
You would have to declare the table variable inside the dynamic SQL statement or create temporary tables.
I would suggest you read this excellent article on dynamic SQL.
http://www.sommarskog.se/dynamic_sql.html
Well, I figured out the way and thought to share with the people out there who might run into the same problem.
Let me start with the problem I had been facing,
I had been trying to execute a Dynamic Sql Statement that used two temporary tables I declared at the top of my stored procedure, but because that dynamic sql statment created a new scope, I couldn't use the temporary tables.
Solution:
I simply changed them to Global Temporary Variables and they worked.
Find my stored procedure underneath.
CREATE PROCEDURE RAFCustom_Room_GetRelatedProducts
-- Add the parameters for the stored procedure here
#PRODUCT_SKU nvarchar(15) = Null
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
IF OBJECT_ID('tempdb..##RelPro', 'U') IS NOT NULL
BEGIN
DROP TABLE ##RelPro
END
Create Table ##RelPro
(
RowID int identity(1,1),
ID int,
Item_Name nvarchar(max),
SKU nvarchar(max),
Vendor nvarchar(max),
Product_Img_180 nvarchar(max),
rpGroup int,
Assoc_Item_1 nvarchar(max),
Assoc_Item_2 nvarchar(max),
Assoc_Item_3 nvarchar(max),
Assoc_Item_4 nvarchar(max),
Assoc_Item_5 nvarchar(max),
Assoc_Item_6 nvarchar(max),
Assoc_Item_7 nvarchar(max),
Assoc_Item_8 nvarchar(max),
Assoc_Item_9 nvarchar(max),
Assoc_Item_10 nvarchar(max)
);
Begin
Insert ##RelPro(ID, Item_Name, SKU, Vendor, Product_Img_180, rpGroup)
Select distinct zp.ProductID, zp.Name, zp.SKU,
(Select m.Name From ZNodeManufacturer m(nolock) Where m.ManufacturerID = zp.ManufacturerID),
'http://s0001.server.com/is/sw11/DG/' +
(Select m.Custom1 From ZNodeManufacturer m(nolock) Where m.ManufacturerID = zp.ManufacturerID) +
'_' + zp.SKU + '_3?$SC_3243$', ep.RoomID
From Product zp(nolock) Inner Join RF_ExtendedProduct ep(nolock) On ep.ProductID = zp.ProductID
Where zp.ActiveInd = 1 And SUBSTRING(zp.SKU, 1, 2) <> 'GC' AND zp.Name <> 'PLATINUM' AND zp.SKU = (Case When #PRODUCT_SKU Is Not Null Then #PRODUCT_SKU Else zp.SKU End)
End
declare #curr_row int = 0,
#tot_rows int= 0,
#sku nvarchar(15) = null;
IF OBJECT_ID('tempdb..##TSku', 'U') IS NOT NULL
BEGIN
DROP TABLE ##TSku
END
Create Table ##TSku (tid int identity(1,1), relsku nvarchar(15));
Select #curr_row = (Select MIN(RowId) From ##RelPro);
Select #tot_rows = (Select MAX(RowId) From ##RelPro);
while #curr_row <= #tot_rows
Begin
select #sku = SKU from ##RelPro where RowID = #curr_row;
truncate table ##TSku;
Insert ##TSku(relsku)
Select distinct top(10) tzp.SKU From Product tzp(nolock) INNER JOIN
[INTRANET].raf_FocusAssociatedItem assoc(nolock) ON assoc.associatedItemID = tzp.SKU
Where (assoc.isActive=1) And (tzp.ActiveInd = 1) AND (assoc.productID = #sku)
declare #curr_row1 int = (Select Min(tid) From ##TSku),
#tot_rows1 int = (Select Max(tid) From ##TSku);
If(#tot_rows1 <> 0)
Begin
While #curr_row1 <= #tot_rows1
Begin
declare #col_name nvarchar(15) = null,
#sqlstat nvarchar(500) = null;
set #col_name = 'Assoc_Item_' + Convert(nvarchar(2), #curr_row1);
set #sqlstat = 'update ##RelPro set ' + #col_name + ' = (Select relsku From ##TSku Where tid = ' + Convert(nvarchar(2), #curr_row1) + ') Where RowID = ' + Convert(nvarchar(2), #curr_row);
Exec(#sqlstat);
set #curr_row1 = #curr_row1 + 1;
End
End
set #curr_row = #curr_row + 1;
End
Select * From ##RelPro;
END
GO
I don't think that is possible (though refer to the update below); as far as I know a table variable only exists within the scope that declared it. You can, however, use a temp table (use the create table syntax and prefix your table name with the # symbol), and that will be accessible within both the scope that creates it and the scope of your dynamic statement.
UPDATE: Refer to Martin Smith's answer for how to use a table-valued parameter to pass a table variable in to a dynamic SQL statement. Also note the limitation mentioned: table-valued parameters are read-only.
Here is an example of using a dynamic T-SQL query and then extracting the results should you have more than one column of returned values (notice the dynamic table name):
DECLARE
#strSQLMain nvarchar(1000),
#recAPD_number_key char(10),
#Census_sub_code varchar(1),
#recAPD_field_name char(100),
#recAPD_table_name char(100),
#NUMBER_KEY varchar(10),
if object_id('[Permits].[dbo].[myTempAPD_Txt]') is not null
DROP TABLE [Permits].[dbo].[myTempAPD_Txt]
CREATE TABLE [Permits].[dbo].[myTempAPD_Txt]
(
[MyCol1] char(10) NULL,
[MyCol2] char(1) NULL,
)
-- an example of what #strSQLMain is : #strSQLMain = SELECT #recAPD_number_key = [NUMBER_KEY], #Census_sub_code=TEXT_029 FROM APD_TXT0 WHERE Number_Key = '01-7212'
SET #strSQLMain = ('INSERT INTO myTempAPD_Txt SELECT [NUMBER_KEY], '+ rtrim(#recAPD_field_name) +' FROM '+ rtrim(#recAPD_table_name) + ' WHERE Number_Key = '''+ rtrim(#Number_Key) +'''')
EXEC (#strSQLMain)
SELECT #recAPD_number_key = MyCol1, #Census_sub_code = MyCol2 from [Permits].[dbo].[myTempAPD_Txt]
DROP TABLE [Permits].[dbo].[myTempAPD_Txt]
Using Temp table solves the problem but I ran into issues using Exec so I went with the following solution of using sp_executesql:
Create TABLE #tempJoin ( Old_ID int, New_ID int);
declare #table_name varchar(128);
declare #strSQL nvarchar(3072);
set #table_name = 'Object';
--build sql sting to execute
set #strSQL='INSERT INTO '+#table_name+' SELECT '+#columns+' FROM #tempJoin CJ
Inner Join '+#table_name+' sourceTbl On CJ.Old_ID = sourceTbl.Object_ID'
**exec sp_executesql #strSQL;**

Using a SP to retrieve information or generate it if data exists in table

I have a requirement in which I need to run a select statement for data in a table if that data exists I need to return the values in a few of the columns. If the data doesn't exist, I need to insert a new row and return the inserted data.
I'll be using an API to execute the stored procedure and return the data, and then write that to a machine via OPC.
What I'm struggling with currently, is that a new entry is created, but does not increment the two columns I need to increment by a digit. In the code below, a new entry will create, but still returns and inputs the same value as the previous lot number. Is there a better way to achieve what I'm wanting to do?
CREATE PROCEDURE [dbo].[Lotconfirmation]
#plcid nvarchar(6) OUTPUT,
#supplierlot nvarchar(25),
#internallotnum nvarchar(25)OUTPUT,
#plclotnum nvarchar(25) OUTPUT,
#suppliercode nvarchar(1),
#supplierpartnum nvarchar(25),
#suppliermodel nvarchar(25),
#qtyconsumed int,
#id int OUTPUT,
#errormsg nvarchar(max) OUTPUT,
#errornum int OUTPUT,
#errorproc nvarchar(max) OUTPUT,
#errorstate int OUTPUT,
#errorline int OUTPUT
AS
BEGIN
BEGIN TRY
/* Check if lot already exists */
SELECT
#internallotnum = InternalLotNum, #plcid = PLCID,
#plclotnum = plclotnum, #id = id
FROM
dbo.ShrinkLotData
WHERE
SupplierMfgLot = #supplierlot
IF #internallotnum IS NULL
BEGIN
DECLARE #table TABLE
(
plcid nvarchar(6),
internallotnum nvarchar(25),
plclotnum nvarchar(25),
id int
)
INSERT INTO dbo.ShrinkLotData(PLCID, SupplierMfgLot, InternalLotNum, PLCLotNum, SupplierCode, SupplierPartNum, SupplierModel, QtyConsumed, Month, Day, Year, TimeStamp)
OUTPUT inserted.plcid, inserted.InternalLotNum, inserted.PLCLotNum, inserted.ID
VALUES (#plcid, #supplierlot,
(SELECT (MAX(InternalLotNum) + 1) FROM dbo.ShrinkLotData),
(SELECT MAX(RIGHT(InternalLotNum, 2) + 1) FROM dbo.ShrinkLotData),
#suppliercode, #supplierpartnum, #suppliermodel,
#qtyconsumed,
MONTH(GETDATE()), DAY(GETDATE()), YEAR(GETDATE()),
CURRENT_TIMESTAMP)
SELECT #plcid = plcid, #internallotnum = internallotnum, #plclotnum = plclotnum, #id = id
FROM #table
END
END TRY
/* E-mail if errors occurs */
BEGIN CATCH
SET #errormsg = 'SP Failed with msg:' + ERROR_MESSAGE()
SET #errornum = ERROR_NUMBER()
SET #errorproc = ERROR_PROCEDURE()
SET #errorstate = ERROR_STATE()
SET #errorline = ERROR_LINE()
/* Place holder to insert fail data into a table
INSERT INTO KSError (datestamp, errormsg, errorproc, errorstate, errorline)
VALUES (#datestamp, #errormsg, #errornum, #errorproc, #errorstate, #errorline)
*/
EXECUTE msdb.dbo.sp_send_dbmail
#recipients = 'email#domain.com',
#profile_name = 'Profile Alert',
#subject = 'KepServer Stored Procedure:',
#body = #errormsg
END CATCH
END
GO
EDIT:
It seems to be working when I cast values as an integer, so I'll need to review those data types and probably just set them up as integers.
(SELECT MAX(CAST(InternalLotNum AS INT)) + 1 FROM dbo.ShrinkLotData),
(SELECT MAX(RIGHT(CAST(InternalLotNum AS Int), 2) + 1) FROM dbo.ShrinkLotData),
While the question is still up, do you guys see a better / more efficient way to do what I'm hoping?
Thanks!
Your OUTPUT clause is returning the values directly to the client, instead of inserting them into your table variable. Should be something like:
INSERT INTO dbo.ShrinkLotData(PLCID, SupplierMfgLot, InternalLotNum, PLCLotNum, SupplierCode, SupplierPartNum, SupplierModel, QtyConsumed, Month, Day, Year, TimeStamp)
OUTPUT inserted.plcid, inserted.InternalLotNum, inserted.PLCLotNum, inserted.ID
INTO #table(plcid,InternalLotNum,PLCLotNum,ID)
VALUES (#plcid, #supplierlot,
(SELECT (MAX(InternalLotNum) + 1) FROM dbo.ShrinkLotData),
(SELECT MAX(RIGHT(InternalLotNum, 2) + 1) FROM dbo.ShrinkLotData),
#suppliercode, #supplierpartnum, #suppliermodel,
#qtyconsumed,
MONTH(GETDATE()), DAY(GETDATE()), YEAR(GETDATE()),
CURRENT_TIMESTAMP)
SELECT #plcid = plcid, #internallotnum = internallotnum, #plclotnum = plclotnum, #id = id
FROM #table

Time slots adding through trigger in SQL Server

I am trying to have the trigger to add time slots in timeslots table each time the doctor insert a record of his availability in the availability table.
I wrote the trigger, it saved. but when I insert a record into availability, the trigger seems not triggered, yet I do not have any error message.
Can somebody help me? Badly needed.
Here is the code for trigger
ALTER TRIGGER [dbo].[trigger_addSlots]
ON [dbo].[availability]
FOR INSERT AS
BEGIN
DECLARE #AvailabilityId INT
DECLARE #SlotStart DATETIME
DECLARE #SlotEnd DATETIME
DECLARE #NumberOfSlots INT
DECLARE #Duration INT
DECLARE #SlotDoctorId INT
DECLARE #i INT
SELECT #AvailabilityId = Id FROM inserted
SELECT #SlotDoctorId = DoctorId FROM inserted
SELECT #SlotStart = AvailableFrom FROM inserted
SELECT #SlotEnd = AvailableTo FROM inserted
SELECT #Duration = AppointmentDuration FROM inserted
SET #NumberOfSlots = CONVERT(INT, (#SlotEnd - #SlotStart)) / #Duration
SET #i = 0;
WHILE #i < #NumberOfSlots
BEGIN
INSERT INTO timeslots(AvailabilityId, SlotStart, SlotEnd, SlotDoctorId, IsAvailable)
VALUES (#AvailabilityId, #SlotStart, #SlotEnd, #SlotDoctorId, 1)
SET #SlotStart = #SlotEnd
SET #i = #i + 1
END
END

Replicate a table record with a new id value using dynamic SQL

I have an application with a requirement that the user can "copy" a record. This will duplicate the record in the table, and the associated records in any child tables.
I will use a trigger to execute a stored procedure to do the copy. The issue I am facing is that I want to increment the ID field for the copied record, which is also the FK in the child tables. The ID field is not a standard format, so using an increment won't work. In order to make this future-proof, I was going to use dynamic SQL to pull the columns for each table so that I don't need to modify the code if I add a new field to one of the tables. The client's system admin can also add columns to the table via the GUI, but they have no access to the SQL backend so they would need to contact us to modify the code (not ideal).
Example:
Declare #ColumnNames varchar(2000)
Declare #BLDGCODE char(4)
set #BLDGCODE = '001'
select #ColumnNames = COALESCE(#ColumnNames + ', ', '') + COLUMN_NAME
from
INFORMATION_SCHEMA.COLUMNS
where
TABLE_NAME='FMB0'
Declare #DynSqlStatement varchar(max);
set #DynSqlStatement = 'Insert into dbo.FMB0('+ #ColumnNames + ')
select * from dbo.FMB0 where BLDGCODE= ' + cast(#BLDGCODE as char(4));
print(#DynSqlStatement);
This solves the issue for a new column being added to one of the tables. However, how can I increment the ID (BLDGCODE in this example). Is my only solution to script out the columns by name so I can increment the ID, or is there a function I am overlooking?
Hopefully this made sense. I am an intermediate SQL user at best, so forgive the naivete if there's an obvious solution.
UPDATE
So I've decided to use #temp tables to hold the record that was changed, modify the id there, and then insert back into the main table from the #temp table. This is working pretty well, with one exception. I get the following error:
The column "FLOORID_" cannot be modified because it is either a computed column or is the result of a UNION operator.
Below is my stored procedure. I've investigated a STUFF approach, but not sure where to insert that code. Using STUFF with calculated column. I am now back to thinking I need to call out the columns specifically for the one table with the computed column, and if we add a new field, I just need to modify this stored procedure. Anyone have any other ideas?
ALTER PROCEDURE [dbo].[LDAC_BLDGCOPY]
#BLDGCODE CHAR(4)
AS
BEGIN
SET NOCOUNT ON;
--COPY BUILDING RECORD
BEGIN
DECLARE #ColumnNamesB0 VARCHAR(2000);
SELECT *
INTO #TEMPB0
FROM FMB0
WHERE BLDGCODE = #BLDGCODE;
UPDATE #TEMPB0
SET BLDGCODE = CONVERT(CHAR(4), (CAST((#BLDGCODE) AS INT) +
100)),
auto_key = dbo.GetAutoKey(),
BLDGCOPY = 0;
SELECT #ColumnNamesB0 = COALESCE(#ColumnNamesB0+', ',
'')+COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'FMB0';
DECLARE #DynSqlStatementB0 VARCHAR(MAX);
SET #DynSqlStatementB0 = 'Insert into dbo.FMB0('+#ColumnNamesB0+')
select * from #TEMPB0';
EXEC (#DynSqlStatementB0);
END;
--COPY FLOOR RECORDS
BEGIN
DECLARE #ColumnNamesL0 VARCHAR(2000);
--DECLARE #Val INT =
RTRIM(CONVERT(CHAR(10),CAST(LEFT(#BL_KEY,LEN(RTRIM(#BL_KEY))-2)+1 as
INT)))+'01
SELECT *
INTO #TEMPL0
FROM FML0
WHERE BLDGCODE = #BLDGCODE;
UPDATE #TEMPL0
SET BLDGCODE = CONVERT(CHAR(4), (CAST((#BLDGCODE) AS INT) +
100)),
auto_key = dbo.GetAutoKey();
UPDATE #TEMPL0
SET FLOORID_ = auto_key;
SELECT #ColumnNamesL0 = COALESCE(#ColumnNamesL0+', ',
'')+COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'FML0';
DECLARE #DynSqlStatementL0 VARCHAR(MAX);
SET #DynSqlStatementL0 = 'Insert into dbo.FML0('+#ColumnNamesL0+')
select * from #TEMPL0';
EXEC (#DynSqlStatementL0);
END;
--COPY ROOM RECORDS
BEGIN
DECLARE #ColumnNamesA0 VARCHAR(2000);
--DECLARE #Val INT =
RTRIM(CONVERT(CHAR(10),CAST(LEFT(#BL_KEY,LEN(RTRIM(#BL_KEY))-2)+1 as
INT)))+'01
SELECT *
INTO #TEMPA0
FROM FMA0
WHERE BLDGCODE = #BLDGCODE;
UPDATE #TEMPA0
SET
BLDGCODE = CONVERT(CHAR(4), (CAST((#BLDGCODE) AS INT) +
100)),
auto_key = dbo.GetAutoKey(),
FLOORID = #TEMPL0.FLOORID_
FROM #TEMPA0
INNER JOIN #TEMPL0 ON CONVERT(CHAR(4), (CAST((#BLDGCODE) AS
INT) + 100)) = #TEMPL0.BLDGCODE;
SELECT #ColumnNamesA0 = COALESCE(#ColumnNamesA0+', ',
'')+COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'FMA0';
DECLARE #DynSqlStatementA0 VARCHAR(MAX);
SET #DynSqlStatementA0 = 'Insert into dbo.FMA0('+#ColumnNamesA0+')
select * from #TEMPA0';
EXEC (#DynSqlStatementA0);
DROP TABLE #TEMPB0;
DROP TABLE #TEMPL0;
DROP TABLE #TEMPA0;
END;
END;

Executing a query on csv data stored in an ntext column

Say that the raw text of CSV exports and an associated timestamps are stored in a database, where one record is equivalent to one export.
Does anyone have a way to execute a query on the CSV file stored in that field without creating a second connection to the database or exporting the data to a file and then reopening it using the csv text driver?
Assume that:
1) you can't write out a physical file onto the server in the solution
2) you can't a second connection to the server w/ OPENROWSET (servers, usernames & passwords change)
3) that it must be a 100% SQL solution - must be able to be run as an SP
4) that you only need to work with one record at time - the solution doesn't need to account for selecting from multiple csv files stored in the DB.
My solution would be to create a UDF that will parse the CSV data into a table variable. Then, in the SP, retrieve the CSV, pass it to the UDF, then run the query against the table variable.
First, create a UDF to return a table from the CSV value (uses CHAR(13) to determine new lines, may need to be altered to work with your data):
CREATE FUNCTION [dbo].[fnParseCSV] (#InputString NVARCHAR(MAX), #Delimiter NCHAR(1) = ',')
RETURNS #tbl TABLE (ID int, Val NVARCHAR(64)) AS
BEGIN
declare #singleLine nvarchar(max)
declare #id int
declare #val varchar(64)
WHILE LEN(#InputString) > 0 BEGIN
IF CHARINDEX(char(13), #InputString) > 0 BEGIN
SELECT #singleLine = SUBSTRING(#InputString, 1, CHARINDEX(char(13), #InputString) - 1)
IF CHARINDEX(#Delimiter, #singleline) > 0 BEGIN
SELECT #id = convert(int, SUBSTRING(#singleline, 1, CHARINDEX(#Delimiter, #singleline) - 1))
SELECT #val = RIGHT(#singleline, LEN(#singleline) - CHARINDEX(#Delimiter, #singleline) )
INSERT INTO #tbl (id, val) values (#id, #val)
END
SELECT #InputString = RIGHT(#InputString, LEN(#InputString) - CHARINDEX(char(13), #InputString) )
END
ELSE
BEGIN
IF CHARINDEX(#Delimiter, #inputString) > 0
BEGIN
SELECT #id = convert(int, SUBSTRING(#inputString, 1, CHARINDEX(#Delimiter, #inputString) - 1))
SELECT #val = RIGHT(#inputString, LEN(#inputString) - CHARINDEX(#Delimiter, #inputString) )
INSERT INTO #tbl (id, val) values (#id, #val)
END
set #inputString = ''
END
END
RETURN
END
Then run the query against that output:
select * from dbo.fnParseCsv('123,val1' + char(13) + '456,val2' + CHAR(13) + '789,val3', ',')
You could set up a series of user-defined functions which could parse through the column. It would likely be slow and wouldn't be robust at all.
As an example though (with no real error checking, etc. and only minimally tested):
IF OBJECT_ID('dbo.Test_CSV_Search') IS NOT NULL
DROP TABLE dbo.Test_CSV_Search
GO
CREATE TABLE dbo.Test_CSV_Search
(
my_id INT IDENTITY NOT NULL,
txt VARCHAR(MAX) NOT NULL,
CONSTRAINT PK_Test_CSV_Search PRIMARY KEY CLUSTERED (my_id)
)
GO
INSERT INTO dbo.Test_CSV_Search (txt) VALUES ('11, 12, 13, 14,15,16
21,22, 23,24, 25,26
31,22,33,34,35,36')
GO
IF OBJECT_ID('dbo.Get_CSV_Row') IS NOT NULL
DROP FUNCTION dbo.Get_CSV_Row
GO
CREATE FUNCTION dbo.Get_CSV_Row
(#my_id INT, #col_num SMALLINT, #search_value VARCHAR(100))
RETURNS #results TABLE (row_num INT, row_txt VARCHAR(MAX))
AS
BEGIN
DECLARE
#csv_txt VARCHAR(MAX),
#full_row VARCHAR(MAX),
#start_pos INT,
#end_pos INT,
#col_txt VARCHAR(100),
#cur_col SMALLINT,
#line_start INT,
#line_end INT,
#row_num INT
SELECT #csv_txt = txt + CHAR(10) FROM dbo.Test_CSV_Search WHERE my_id = #my_id
SELECT
#line_start = 1,
#cur_col = 1,
#start_pos = 1,
#row_num = 1
WHILE (CHARINDEX(CHAR(10), #csv_txt, #line_start) > 0)
BEGIN
SELECT
#line_end = CHARINDEX(CHAR(10), #csv_txt, #line_start),
#end_pos = CHARINDEX(',', #csv_txt, #start_pos)
WHILE (#cur_col < #col_num)
BEGIN
SET #start_pos = #end_pos + 1
SET #end_pos = CHARINDEX(',', #csv_txt, #start_pos)
SET #cur_col = #cur_col + 1
END
IF (RTRIM(LTRIM(SUBSTRING(#csv_txt, #start_pos, #end_pos - #start_pos))) = #search_value)
BEGIN
INSERT INTO #results (row_num, row_txt) VALUES (#row_num, RTRIM(LTRIM(SUBSTRING(#csv_txt, #line_start, #line_end - #line_start))))
END
SELECT
#line_start = #line_end + 1,
#start_pos = #line_end + 1,
#cur_col = 1,
#row_num = #row_num + 1
END
RETURN
END
GO
SELECT * FROM dbo.Get_CSV_Row(1, 1, '11')

Resources