size of a sql table variable without using DATALENGTH - sql

How can I determine the space used by a table variable without using DATALENGTH on all columns?
eg:
DECLARE #T TABLE
(
a bigint,
b bigint,
c int,
d varchar(max)
)
insert into #T select 1,2,3, 'abc123'
exec sp_spaceused #T
Trying to work out how much memory a Table variable consumes when running a stored procedure.
I know in this example I can go:
SELECT DATALENGTH(a) + DATALENGTH(b) + DATALENGTH(c) + DATALENGTH(d)
But is there any other way other than doing DATALENGTH on all table columns?

The metadata for table variables is pretty much the same as for other types of tables so you can determine space used by looking in various system views in tempdb.
The main obstacle is that the table variable will be given an auto generated name such as #3D7E1B63 and I'm not sure if there is a straight forward way of determining its object_id.
The code below uses the undocumented %%physloc%% function (requires SQL Server 2008+) to determine a data page belonging to the table variable then DBCC PAGE to get the associated object_id. It then executes code copied directly from the sp_spaceused procedure to return the results.
DECLARE #T TABLE
(
a bigint,
b bigint,
c int,
d varchar(max)
)
insert into #T select 1,2,3, 'abc123'
DECLARE #DynSQL nvarchar(100)
SELECT TOP (1) #DynSQL = 'DBCC PAGE(2,' +
CAST(file_id AS VARCHAR) + ',' +
CAST(page_id AS VARCHAR) + ',1) WITH TABLERESULTS'
FROM #T
CROSS APPLY sys.fn_PhysLocCracker(%%physloc%%)
DECLARE #DBCCPage TABLE (
[ParentObject] [varchar](100) NULL,
[Object] [varchar](100) NULL,
[Field] [varchar](100) NULL,
[VALUE] [varchar](100) NULL
)
INSERT INTO #DBCCPage
EXEC (#DynSQL)
DECLARE #id int
SELECT #id = VALUE
FROM #DBCCPage
WHERE Field = 'Metadata: ObjectId'
EXEC sp_executesql N'
USE tempdb
declare #type character(2) -- The object type.
,#pages bigint -- Working variable for size calc.
,#dbname sysname
,#dbsize bigint
,#logsize bigint
,#reservedpages bigint
,#usedpages bigint
,#rowCount bigint
/*
** Now calculate the summary data.
* Note that LOB Data and Row-overflow Data are counted as Data Pages.
*/
SELECT
#reservedpages = SUM (reserved_page_count),
#usedpages = SUM (used_page_count),
#pages = SUM (
CASE
WHEN (index_id < 2) THEN (in_row_data_page_count + lob_used_page_count + row_overflow_used_page_count)
ELSE lob_used_page_count + row_overflow_used_page_count
END
),
#rowCount = SUM (
CASE
WHEN (index_id < 2) THEN row_count
ELSE 0
END
)
FROM sys.dm_db_partition_stats
WHERE object_id = #id;
/*
** Check if table has XML Indexes or Fulltext Indexes which use internal tables tied to this table
*/
IF (SELECT count(*) FROM sys.internal_tables WHERE parent_id = #id AND internal_type IN (202,204,211,212,213,214,215,216)) > 0
BEGIN
/*
** Now calculate the summary data. Row counts in these internal tables don''t
** contribute towards row count of original table.
*/
SELECT
#reservedpages = #reservedpages + sum(reserved_page_count),
#usedpages = #usedpages + sum(used_page_count)
FROM sys.dm_db_partition_stats p, sys.internal_tables it
WHERE it.parent_id = #id AND it.internal_type IN (202,204,211,212,213,214,215,216) AND p.object_id = it.object_id;
END
SELECT
name = OBJECT_NAME (#id),
rows = convert (char(11), #rowCount),
reserved = LTRIM (STR (#reservedpages * 8, 15, 0) + '' KB''),
data = LTRIM (STR (#pages * 8, 15, 0) + '' KB''),
index_size = LTRIM (STR ((CASE WHEN #usedpages > #pages THEN (#usedpages - #pages) ELSE 0 END) * 8, 15, 0) + '' KB''),
unused = LTRIM (STR ((CASE WHEN #reservedpages > #usedpages THEN (#reservedpages - #usedpages) ELSE 0 END) * 8, 15, 0) + '' KB'')
', N'#id int',#id=#id
Returns
name rows reserved data index_size unused
------------------------------ ----------- ------------------ ------------------ ------------------ ------------------
#451F3D2B 1 16 KB 8 KB 8 KB 0 KB

Related

SQL - query for combining multiple rows

I'm searching but i can't find a solution for my problem.
I've a table (say T1) with this data:
SampleCode
Name
Content
#
1
A#
#
2
B#
#
3
C#
#
1
A#
#
2
B#
#
3
C#
So i need a select query resulting in :
Column 1
Column 2
Column 3
SampleCode
#
#
Name.1
A#
A#
Name.2
B#
B#
Name.3
C#
C#
Does anyone has an hint?
Thanks
Fabio
Check out the 2 options in this article: https://www.sqlshack.com/multiple-options-to-transposing-rows-into-columns/
If using TSQL, you could use a TRANSPOSE statement. Or you can create the result set using cursors/loops and dynamic SQL.
Fully Dynamic Solution
I can't believe I hammered this out... maybe I did it just to see if I could. It's long and complicated and uses several loops. I think you should re-evaluate what you're trying to do. This can't be run in a view. You would have to put into a function or stored procedure. You may want to evaluate the temp table names to see if they are compatible with your environment. Also, since the temp table name are not unique, you cannot run multiple instances; if you want to run multiple instances, you would have to add a unique identifier to a column in the temp tables or to temp table names. This is fully dynamic... you don't have to know how many columns you need ahead of time.
--**************************************************
--Header area... sample data and temp tables.
--**************************************************
--Clean up sample and result tables if they exist.
IF OBJECT_ID(N'tempdb..#T1') IS NOT NULL
DROP TABLE #T1;
IF OBJECT_ID(N'tempdb..#ResultsTemp') IS NOT NULL
DROP TABLE #ResultsTemp;
IF OBJECT_ID(N'tempdb..#codeTable') IS NOT NULL
DROP TABLE #codeTable;
--For Debugging.
DECLARE #debug int = 1; --0=no debug, 1=show debug messages, do not exec all SQL statements.
--Table var to hold sample data.
CREATE TABLE #T1 (
SampleCode nvarchar(50),
[Name] nvarchar(50),
Content nvarchar(50),
row_num int
);
--Load Sample Data.
INSERT INTO #T1 (SampleCode, [Name], Content)
VALUES
('#', '1', 'A#')
, ('#', '2', 'B#')
, ('#', '3', 'C#')
, ('#', '1', 'A#')
, ('#', '2', 'B#')
, ('#', '3', 'C#')
;
--**********END HEADER**********
--Number the rows so we can loop over them.
UPDATE #T1
SET row_num = newT1.row_num
FROM
(
SELECT t12.SampleCode
, t12.[Name]
, T12.Content
, ROW_NUMBER()OVER(ORDER BY SampleCode, [Name], Content) as row_num
FROM #T1 as t12
) AS newT1
WHERE #T1.SampleCode = newT1.SampleCode
AND #T1.[Name] = newT1.[Name]
AND #T1.Content = newT1.Content
;
SELECT * FROM #T1; --Debugging... just show the contents of #T1 after adding row_num
--Create temp table to load results.
CREATE TABLE #ResultsTemp (
Column1 nvarchar(50)
);
--Create some variable to track looping (without a cursor).
DECLARE #loopCount int = 1;
DECLARE #maxLoops int = (SELECT COUNT(DISTINCT SampleCode) FROM #T1);
DECLARE #sql nvarchar(512);
--Add columns to #ResultsTable dynamically
WHILE (#loopCount <= (#maxLoops))
BEGIN
SET #sql = 'ALTER TABLE #ResultsTemp ADD ' + QUOTENAME('Column' + CAST((#loopCount + 1) as nvarchar)) + ' nvarchar(50) NULL';
IF (#debug = 1) PRINT #sql;
EXEC (#sql);
SET #loopCount = #loopCount + 1;
END
--************************************************************
--SECTION FOR INSERTING FIRST ROW for "SampleCode"
--************************************************************
--Vars for tracking the SampleCode variations processed.
DECLARE #sampleLoop int = 1;
DECLARE #sampleCount int = (SELECT COUNT(DISTINCT SampleCode) FROM #T1);
CREATE TABLE #codeTable (
SampleCode nvarchar(50)
, row_num int
);
--Create a list of unique SampleCodes to loop over.
INSERT INTO #codeTable (SampleCode)
SELECT DISTINCT
SampleCode
FROM #T1;
UPDATE #codeTable
SET row_num = newCT.row_num
FROM
(
SELECT ct2.SampleCode
, ROW_NUMBER()OVER(ORDER BY SampleCode) as row_num
FROM #codeTable as ct2
) AS newCT
WHERE #codeTable.SampleCode = newCT.SampleCode
;
SET #sql = 'INSERT INTO #ResultsTemp (Column1) SELECT ''SampleCode''';
IF (#debug = 1) PRINT #sql;
EXEC (#sql);
WHILE (#sampleLoop <= #sampleCount)
BEGIN
SET #sql = 'UPDATE #ResultsTemp SET Column' + CAST(#SampleLoop + 1 as nvarchar) + '=(SELECT TOP 1 SampleCode FROM #codeTable WHERE row_num = ' + CAST(#sampleLoop as nvarchar) + ');';
IF (#debug = 1) PRINT #sql;
EXEC (#sql);
SET #sampleLoop = #sampleLoop + 1;
END
IF (#debug = 1) SELECT * FROM #ResultsTemp;
--**********END SECTION**********
--**************************************************
--SECTION FOR INSERTING THE REST OF THE CONTENT DATA
--**************************************************
--Vars for tracking number of rows processed from T1.
SET #loopCount = 1;
SET #maxLoops = (SELECT MAX(row_num) FROM #T1);
--Loop over each row in T1.
WHILE (#loopCount <= #maxLoops)
BEGIN
--Create a name for this row.
DECLARE #nameRaw nvarchar(50) = (SELECT TOP 1 [Name] FROM #T1 WHERE row_num = #loopCount);
DECLARE #codeNum nvarchar(50) = (
SELECT TOP 1 ct.row_num
FROM #T1 as t
INNER JOIN #codeTable as ct
ON ct.SampleCode = t.SampleCode
WHERE t.row_num = #loopCount);
DECLARE #name nvarchar(50) = 'Name.' + #nameRaw;
--First insert a row for this Name... if not already in the list.
SET #sql = 'INSERT INTO #ResultsTemp (Column1)
SELECT ''Name.'' + t.[Name]
FROM #T1 as t
LEFT OUTER JOIN #ResultsTemp as rt
ON rt.Column1 = ''' + #name + '''
WHERE t.row_num = ' + CAST(#loopCount as nvarchar) + '
AND rt.Column1 IS NULL;';
IF (#debug = 1) PRINT #sql;
EXEC (#sql);
--Update this Name row with the "content".
SET #sql = 'UPDATE rt
SET Column' + CAST(#codeNum + 1 as nvarchar) + '=t.Content
FROM #ResultsTemp as rt
INNER JOIN #T1 as t
ON t.row_num = ' + CAST(#loopCount as nvarchar) + '
AND t.[Name] = ''' + #nameRaw + '''
WHERE rt.Column1 = ''' + #name + ''';';
IF (#debug = 1) PRINT #sql;
EXEC (#sql);
SET #loopCount = #loopCount + 1;
END
--Show everything in the temp Results table.
SELECT *
FROM #ResultsTemp;
Result Set:
Static Select Solution
This next option is not dynamic. You have to know how many columns you need and then you can copy the "Column3" code to any new columns. You have to change the column name and update the "Select TOP 1" statement as commented for each new column you would copy.
WITH CodeTable AS (
SELECT DISTINCT t.SampleCode
FROM #T1 as t
)
SELECT DISTINCT
'Name.' + [Name]
, (
SELECT TOP 1 Content
FROM #T1 as t2
WHERE t2.SampleCode = (
SELECT TOP 1 SampleCode
FROM CodeTable as ct
ORDER BY SampleCode
)
AND t2.[Name] = t.[Name]
) as Column2
, (
SELECT TOP 1 Content
FROM #T1 as t2
WHERE t2.SampleCode = (
SELECT TOP 1 SampleCode
FROM CodeTable as ct
WHERE ct.SampleCode NOT IN (
SELECT TOP 1 SampleCode --Update the TOP 1 to be TOP [number of previous columns]
FROM CodeTable as ct2
ORDER BY ct2.SampleCode
)
ORDER BY ct.SampleCode
)
AND t2.[Name] = t.[Name]
) as Column3
FROM #T1 as t
Static PivotTable Solution
This solution is from the link I posted. Again, you have to know the number of columns you need and it doesn't have generic column names like you specify. But, it is another solution if you reconsider your output.
--Use PivotTable.
SELECT *
FROM
(
SELECT [Name], Content
FROM #T1
) AS SourceTable PIVOT(MAX(Content) FOR [Content] IN([A#],
[A#]
)) AS PivotTable;
Pivot table can solve your prooblem:
DECLARE #T TABLE (SampleCode sysname, Name sysname, Content sysname)
INSERT #T (SampleCode, Name, Content)
VALUES
('#', '1', 'A#'),
('#', '2', 'B#'),
('#', '3', 'C#'),
('#', '1', 'A#'),
('#', '2', 'B#'),
('#', '3', 'C#')
SELECT CONCAT('Name.', PIVOTTABLE.Name), [#], [#]
FROM
(
SELECT * FROM #T AS t
) AS SOURCE
PIVOT (
MAX(Content)
FOR SampleCode IN ([#], [#])
) AS PIVOTTABLE
Of course this is the solution for what you provided in the question.
When the columns are too many to manually write down then use Dynamic query and the only parts needed to be generate is the values in FOR expression and the same value in SELECT

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

Issue In Changing Column Names using SQL Server

It's my first question on Stack Overflow; I am facing a problem in changing column names.
I have a table INS_Sink with columns like
INS2017, INS2018, INS2019, INS2020
Now I need to change the existing column names like this:
INS2017 to INS CY-3,
INS2018 to INS CY-2,
INS2019 to INS CY-1,
INS2020 to INS CY,
where CY = CurrentYear.
I need to change all the columns dynamically like the above mentioned and the column names should be changed automatically when the year gets changed like in the year 2021, INS2020 should become INS CY-1..
Can anyone help me how to achieve this and I don't want to alter my columns here?
Thanks in advance...
First, you should not do this. The underlying table should reflect the correct data.
Second, you should not use names like INS CY-1. Such a name needs to be escaped. Instead, you should use INS_CY_1. That is cleaner.
Next, you have a problem with your data model. You should be storing the data as:
YEAR INSVALUE
2017 ?
2018 ?
Of course, you might have other columns that you haven't specified.
With this data structure, it is easy to create a view that does what you want. Say, the current year is found using YEAR(GETDATE()). Then:
select sum(case when t.year = v.yyyy then t.insvalue end) as ins_cy,
sum(case when t.year = v.yyyy - 1 then t.insvalue end) as ins_cy_1,
sum(case when t.year = v.yyyy then t.insvalue end) as ins_cy_2
from t cross join
(values (year(getdate())) v(yyyy);
You can continue this logic for however many years you want to go back in time.
if you really need to get the data in CY-xyz columns then it might be better to create a view and adjust the view when needed (you could use both the view and the table for DML operations).
create table dbo.INS_Sink_test
(
id int identity(1,1) constraint pk_id_ins_sink_test primary key clustered,
col1 int,
col2 int,
INS2010 int,
INS2011 int,
INS2012 int,
INS2013 int,
INS2014 int,
INS2015 int,
INS2016 int,
INS2017 int,
INS2018 int,
INS2019 int,
INS2020 int
)
go
insert into dbo.INS_Sink_test
(
col1, col2, INS2010, INS2011, INS2012, INS2013, INS2014, INS2015, INS2016, INS2017, INS2018, INS2019, INS2020
)
values
(1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4)
go
--create/adjust a view
declare #year smallint = year(getdate());
newyear:
declare #colssql nvarchar(max) = N'';
select #colssql = #colssql + N',' + quotename(name) + ' as ' + quotename(newcolname)
from
(
select column_id,
name,
case when name like 'INS[0-9][0-9][0-9][0-9]' then 'CY' + isnull(cast(nullif(cast(replace(name, 'INS', '') as smallint)-#year, 0) as sysname), '')
else name
end as newcolname
from sys.columns
where object_id = object_id('dbo.INS_Sink_test')
) as a
order by column_id;
declare #viewsql nvarchar(max) = N'view dbo.v_INS_Sink_test
as
select
' + stuff(#colssql, 1, 1, N'') + N'
from dbo.INS_Sink_test
';
select #viewsql = case when object_id('dbo.v_INS_Sink_test') is null then N'create' else N'alter' end + N' ' + #viewsql ;
--print #viewsql
exec(#viewsql);
--dont forget to refresh other views that depend on v_ins_sink_test
--exec sp_refreshview 'dbo.view_xyz';
exec sp_refreshview 'dbo.v_INS_Sink_test';
select #year as theyear;
select *
from dbo.INS_Sink_test;
select *
from dbo.v_INS_Sink_test;
if #year = 2021
begin
--cleanup
if object_id('dbo.INS_Sink_test') is not null
begin
drop table dbo.INS_Sink_test;
end
if object_id('dbo.v_INS_Sink_test') is not null
begin
drop view dbo.v_INS_Sink_test;
end
return;
end
--i know what you did in 2021..
select #year = 2021;
goto newyear
--cleanup
/*
if object_id('dbo.INS_Sink_test') is not null
begin
drop table dbo.INS_Sink_test;
end
if object_id('dbo.v_INS_Sink_test') is not null
begin
drop view dbo.v_INS_Sink_test;
end
*/
For changing the column names of the table:
create table dbo.INS_Sink_test
(
id int identity(1,1) constraint pk_id_ins_sink_test primary key clustered,
col1 int,
col2 int,
INS2010 int,
INS2011 int,
INS2012 int,
INS2013 int,
INS2014 int,
INS2015 int,
INS2016 int,
INS2017 int,
INS2018 int,
INS2019 int,
INS2020 int
)
go
insert into dbo.INS_Sink_test
(
col1, col2, INS2010, INS2011, INS2012, INS2013, INS2014, INS2015, INS2016, INS2017, INS2018, INS2019, INS2020
)
values
(1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4)
go
select *
from dbo.INS_Sink_test
go
--rename columns
declare #year smallint = year(getdate());
declare #renamecolssql nvarchar(max) = N'';
select #renamecolssql = #renamecolssql + N'exec sp_rename ''' + quotename(object_schema_name(object_id)) + N'.' + quotename(object_name(object_id)) + N'.' + quotename(name) + ''', '+ quotename(newcolname) + '; '
from
(
select
object_id,
column_id,
name,
'INS CY' + isnull(cast(nullif(cast(replace(name, 'INS', '') as smallint)-#year, 0) as sysname), '') as newcolname
from sys.columns
where object_id = object_id('dbo.INS_Sink_test') --<-- change the name of the table
and name like 'INS[0-9][0-9][0-9][0-9]'
) as a
order by column_id;
exec(#renamecolssql);
go
select *
from dbo.INS_Sink_test
go
--cleanup
drop table dbo.INS_Sink_test
go

SQL CSV as Query Results Column

I have the following SQL which queries a single table, single row, and returns the results as a comma separate string e.g.
Forms
1, 10, 4
SQL :
DECLARE #tmp varchar(250)
SET #tmp = ''
SELECT #tmp = #tmp + Form_Number + ', '
FROM Facility_EI_Forms_Required
WHERE Facility_ID = 11 AND EI_Year=2012 -- single Facility, single year
SELECT SUBSTRING(#tmp, 1, LEN(#tmp) - 1) AS Forms
The Facility_EI_Forms_Required table has three records for Facility_ID = 11
Facility_ID EI_Year Form_Number
11 2012 1
11 2012 10
11 2012 4
Form_number is a varchar field.
And I have a Facility table with Facility_ID and Facility_Name++.
How do I create a query to query all Facilites for a given year and produce the CSV output field?
I have this so far:
DECLARE #tmp varchar(250)
SET #tmp = ''
SELECT TOP 100 A.Facility_ID, A.Facility_Name,
(
SELECT #tmp = #tmp + B.Form_Number + ', '
FROM B
WHERE B.Facility_ID = A.Facility_ID
AND B.EI_Year=2012
)
FROM Facility A, Facility_EI_Forms_Required B
But it gets syntax errors on using #tmp
My guess is this is too complex a task for a query and a stored procedure may be need, but I have little knowledge of SPs. Can this be done with a nested query?
I tried a Scalar Value Function
ALTER FUNCTION [dbo].[sp_func_EI_Form_List]
(
-- Add the parameters for the function here
#p1 int,
#pYr int
)
RETURNS varchar
AS
BEGIN
-- Declare the return variable here
DECLARE #Result varchar
-- Add the T-SQL statements to compute the return value here
DECLARE #tmp varchar(250)
SET #tmp = ''
SELECT #tmp = #tmp + Form_Number + ', '
FROM OIS..Facility_EI_Forms_Required
WHERE Facility_ID = #p1 AND EI_Year = #pYr -- single Facility, single year
SELECT #Result = #tmp -- SUBSTRING(#tmp, 1, LEN(#tmp) - 1)-- #p1
-- Return the result of the function
RETURN #Result
END
The call
select Facility_ID, Facility.Facility_Name,
dbo.sp_func_EI_Form_List(Facility_ID,2012)
from facility where Facility_ID=11
returns
Facility_ID Facility_Name Form_List
11 Hanson Aggregates 1
so it is only returning the first record instead of all three. What am I doing wrong?
Try the following approach, which is an analogy to SO answer Concatenate many rows into a single text string. I hope it is correct, as I cannot try it out without having the schema and some demo data (maybe you can add schema and data to your question):
Select distinct A.Facility_ID, A.Facility_Name,
substring(
(
Select ',' + B.Form_Number AS [text()]
From Facility_EI_Forms_Required B
Where B.Facility_ID = A.Facility_ID
AND B.EI_Year=2012
ORDER BY B.Facility_ID
For XML PATH ('')
), 2, 1000) [Form_List]
From Facility A

Convert Comma Delimited String to bigint in SQL Server

I have a varchar string of delimited numbers separated by commas that I want to use in my SQL script but I need to compare with a bigint field in the database. Need to know to convert it:
DECLARE #RegionID varchar(200) = null
SET #RegionID = '853,834,16,467,841,460,495,44,859,457,437,836,864,434,86,838,458,472,832,433,142,154,159,839,831,469,442,275,840,299,446,220,300,225,227,447,301,450,230,837,441,835,302,477,855,411,395,279,303'
SELECT a.ClassAdID, -- 1
a.AdURL, -- 2
a.AdTitle, -- 3
a.ClassAdCatID, -- 4
b.ClassAdCat, -- 5
a.Img1, -- 6
a.AdText, -- 7
a.MemberID, -- 9
a.Viewed, -- 10
c.Domain, -- 11
a.CreateDate -- 12
FROM ClassAd a
INNER JOIN ClassAdCat b ON b.ClassAdCAtID = a.ClassAdCAtID
INNER JOIN Region c ON c.RegionID = a.RegionID
AND a.PostType = 'CPN'
AND DATEDIFF(d, GETDATE(), ExpirationDate) >= 0
AND a.RegionID IN (#RegionID)
AND Viewable = 'Y'
This fails with the following error:
Error converting data type varchar to bigint.
RegionID In the database is a bigint field.. need to convert the varchar to bigint.. any ideas..?
Many thanks in advance,
neojakey
create this function:
CREATE function [dbo].[f_split]
(
#param nvarchar(max),
#delimiter char(1)
)
returns #t table (val nvarchar(max), seq int)
as
begin
set #param += #delimiter
;with a as
(
select cast(1 as bigint) f, charindex(#delimiter, #param) t, 1 seq
union all
select t + 1, charindex(#delimiter, #param, t + 1), seq + 1
from a
where charindex(#delimiter, #param, t + 1) > 0
)
insert #t
select substring(#param, f, t - f), seq from a
option (maxrecursion 0)
return
end
change this part:
AND a.RegionID IN (select val from dbo.f_split(#regionID, ','))
Change this for better overall performance:
AND DATEDIFF(d, 0, GETDATE()) <= ExpirationDate
Your query does not know that those are separate values, you can use dynamic sql for this:
DECLARE #RegionID varchar(200) = null
SET #RegionID = '853,834,16,467,841,460,495,44,859,457,437,836,864,434,86,838,458,472,832,433,142,154,159,839,831,469,442,275,840,299,446,220,300,225,227,447,301,450,230,837,441,835,302,477,855,411,395,279,303'
declare #sql nvarchar(Max)
set #sql = 'SELECT a.ClassAdID, -- 1
a.AdURL, -- 2
a.AdTitle, -- 3
a.ClassAdCatID, -- 4
b.ClassAdCat, -- 5
a.Img1, -- 6
a.AdText, -- 7
a.MemberID, -- 9
a.Viewed, -- 10
c.Domain, -- 11
a.CreateDate -- 12
FROM ClassAd a
INNER JOIN ClassAdCat b ON b.ClassAdCAtID = a.ClassAdCAtID
INNER JOIN Region c ON c.RegionID = a.RegionID
AND a.PostType = ''CPN''
AND DATEDIFF(d, GETDATE(), ExpirationDate) >= 0
AND a.RegionID IN ('+#RegionID+')
AND Viewable = ''Y'''
exec sp_executesql #sql
I use this apporach sometimes and find it very good.
It transfors your comma-separated string into an AUX table (called #ARRAY) and then query the main table based on the AUX table:
declare #RegionID varchar(50)
SET #RegionID = '853,834,16,467,841,460,495,44,859,457,437,836,864,434,86,838,458,472,832,433,142,154,159,839,831,469,442,275,840,299,446,220,300,225,227,447,301,450,230,837,441,835,302,477,855,411,395,279,303'
declare #S varchar(20)
if LEN(#RegionID) > 0 SET #RegionID = #RegionID + ','
CREATE TABLE #ARRAY(region_ID VARCHAR(20))
WHILE LEN(#RegionID) > 0 BEGIN
SELECT #S = LTRIM(SUBSTRING(#RegionID, 1, CHARINDEX(',', #RegionID) - 1))
INSERT INTO #ARRAY (region_ID) VALUES (#S)
SELECT #RegionID = SUBSTRING(#RegionID, CHARINDEX(',', #RegionID) + 1, LEN(#RegionID))
END
select * from your_table
where regionID IN (select region_ID from #ARRAY)
It avoids you from ahving to concatenate the query string and then use EXEC to execute it, which I dont think it is a very good approach.
if you need to run the code twice you will need to drop the temp table
I think the answer should be kept simple.
Try using CHARINDEX like this:
DECLARE #RegionID VARCHAR(200) = NULL
SET #RegionID =
'853,834,16,467,841,460,495,44,859,457,437,836,864,434,86,838,458,472,832,433,142,154,159,839,831,469,442,275,840,299,446,220,300,225,227,447,301,450,230,837,441,835,302,477,855,411,395,279,303'
SELECT 1
WHERE Charindex('834', #RegionID) > 0
SELECT 1
WHERE Charindex('999', #RegionID) > 0
When CHARINDEX finds the value in the large string variable, it will return it's position, otherwise it return 0.
Use this as a search tool.
The easiest way to change this query is to replace the IN function with a string function. Here is what I consider the safest approach using LIKE (which is portable among databases):
AND ','+#RegionID+',' like '%,'+cast(a.RegionID as varchar(255))+',%'
Or CHARINDEX:
AND charindex(','+cast(a.RegionID as varchar(255))+',', ','+#RegionID+',') > 0
However, if you are explicitly putting the list in your code, why not use a temporary table?
declare #RegionIds table (RegionId int);
insert into #RegionIds
select 853 union all
select 834 union all
. . .
select 303
Then you can use the table in the IN clause:
AND a.RegionId in (select RegionId from #RegionIds)
or in a JOIN clause.
I like Diego's answer some, but I think my modification is a little better because you are declaring a table variable and not creating an actual table. I know the "in" statement can be a little slow, so I did an inner join since I needed some info from the Company table anyway.
declare #companyIdList varchar(1000)
set #companyIdList = '1,2,3'
if LEN(#companyIdList) > 0 SET #companyIdList = #companyIdList + ','
declare #CompanyIds TABLE (CompanyId bigint)
declare #S varchar(20)
WHILE LEN(#companyIdList) > 0 BEGIN
SELECT #S = LTRIM(SUBSTRING(#companyIdList, 1, CHARINDEX(',', #companyIdList) - 1))
INSERT INTO #CompanyIds (CompanyId) VALUES (#S)
SELECT #companyIdList = SUBSTRING(#companyIdList, CHARINDEX(',', #companyIdList) + 1, LEN(#companyIdList))
END
select d.Id, d.Name, c.Id, c.Name
from [Division] d
inner join [Company] c on d.CompanyId = c.Id
inner join #CompanyIds cids on c.Id = cids.CompanyId