i need to concatenate all rows to display in a single string
i wrote the same query for other table it works
both queries gives me a last row value only
declare #varaibelName nvarchar(max)
set #cpx=''
Select #varaibelName=COALESCE(#varaibelName,'')+ FeildName From TableName
select #varaibelName
or
declare #varaibelName nvarchar(max)
set #varaibelName=''
Select #varaibelName=#varaibelName+ FeildName From TableName
select #varaibelName
That would be because your second to last value is NULL, so it'll effectively set the parameter to NULL, and then fetch the last remaining row.
See fiddle here for example.
To fix that, you can prepare for the null with for example ISNULL, like so (replace the SQL in the fiddle with this, and see what happens):
declare #variableName nvarchar(max)
Select #variableName=COALESCE(#variableName,'')+ ISNULL(FieldName,'') From TableName
select #variableName
Better yet, you probably want to separate the columns from each other, so you can do this to add a comma inbetween them, then parse the extra comma off from the end of the string:
declare #variableName nvarchar(max)
Select #variableName=COALESCE(#variableName,'')+ ISNULL(FieldName+', ','') From TableName
select #variableName = SUBSTRING(#variableName, 1, LEN(#variableName)-1)
select #variableName
EDIT:
HoneyBadger is absolutely right, in his comment below. Since I'm having a brainfart day regarding COALESCE, might as well go with this to avoid pointlessly using both:
declare #variableName nvarchar(max) set #variableName = ''
Select #variableName= #variableName + ISNULL(FieldName+', ','') From TableName
select #variableName = SUBSTRING(#variableName, 1, LEN(#variableName)-1)
select #variableName
Related
I'm using a search object query (found on the internet, wish I could give credit to the developer) to search database for the columns needed when I write queries. The output search object query allows me to enter the type of table to look in (partial name) as well as the column name (partial name) I'm trying to find. I've been attempting to modify the search object query so it returns the 1st value (Top 1) it finds. This would help me to easily see at a glance if the column has the particular type of data I'm looking for.
I've attempted to write it both as a stored procedure that I could pass two parameters (partial table and partial column name) and I've also tried using dynamic SQL (my first attempt at using it, so I'm a novice when it comes to use it). I had moderate success with the use of dynamic SQL, but can only get it to produce one result rather than be called multiple times for all the results in my search object output. The code I used is shown here:
-- This is the search object query found on internet
Use masterdb
Select a.name, b.name
From sysobjects a
Inner Join syscolumns b On a.id = b.id
Where b.name like '%Result%'
And a.name like '%Lab%'
Order By a.name, b.name
-- This is a separate query I used to test calling the data with dynamic SQL
DECLARE #value VARCHAR(100), #tablename VARCHAR(100)
SET #value = 'Result'
SET #tablename = 'LabSpecimen'
DECLARE #sqlText NVARCHAR(1000);
SET #sqlText = N'SELECT Top 1 ' + #value + ' FROM testndb.dbo.' + #tablename
EXEC (#sqlText)
If I use the search object query and search for tables that have lab and column names that have result, I might get output like this:
LabMain,ResultID
LabSpecimen,ResultCategory
LabSpecimen,ResultDate
LabSpecimen,Results
I would like to have the search object query pull data from the table in the first column and the column name in the 2nd column and return the first value it finds to give me a sample output for the given column name/table. Output would look like this:
LabMain,ResultID,E201812310001
LabSpecimen,ResultCategory,ExampleCategory
LabSpecimen,ResultDate,20181231
LabSpecimen,Results,34.20
Okay, I really didn't want to have to post an answer to this, but here goes.
So, the first, really-really-huge thing is: SQL Injection. SQL Injection is the #1 security vulnerability for something like a dozen years running, per OWASP. Basically, SQL Injection is where you use dynamic SQL that has any fragment of the sql command being populated by a user. So in the OP's case, this section here:
SET #value = 'Result'
SET #tablename = 'LabSpecimen'
DECLARE #sqlText NVARCHAR(1000);
SET #sqlText = N'SELECT Top 1 ' + #value + ' FROM testndb.dbo.' + #tablename
EXEC (#sqlText)
... if the end incarnation would be that #tableName and #value are populated by the user as part of their search? Then the user can do a 'search' that ends up injecting sql statements that the server runs directly; for a cheap example, imagine this for #value:
3' ; drop table #tableName --
... which would go ahead and drop every table that matches the #tablename you passed in.
Anyway, so, as we go through this problem, we're going to keep SQL Injection in mind at every step.
Problem #1: How to get the tables/columns that match.
You pretty much already nailed this. The only thing missing is to put it into a temp table so that you can loop through it (and limit it down to U-types, since otherwise you'll get stored procs and system tables.) I went ahead and had it also hit the Schema information - that way, if you have tables in different schemas, it'll still be able to get the results.
declare #tableNameFragment varchar(100) -- note: these top 4 lines will eventually
declare #columnNameFragment varchar(100) -- be changed to stored proc args
set #tableNameFragment = 'Performance' -- and populated by the user calling
set #columnNameFragment = 'status' -- the proc (instead of hard-coded.)
declare #entityMatches TABLE (TableName varchar(200), ColName varchar(128))
insert into #entityMatches
Select sch.TABLE_SCHEMA + '.' + sysobj.name as TableName, syscol.name as ColName
From sysobjects sysobj
Join syscolumns syscol On sysobj.id = syscol.id
Join INFORMATION_SCHEMA.TABLES sch on sch.TABLE_NAME = sysobj.name
where sysobj.xtype = 'U'
and (sysobj.name like '%' + isnull(#tableNameFragment,'') + '%')
and (syscol.name like '%' + isnull(#columnNameFragment,'') + '%')
Now, notice that while #tableNameFragment and #columnNameFragment are used, they're not used in a dynamic query. It doesn't matter if the user puts in something malicious into those values
Problem #2 - How to loop through your table
Basically, you're going to need a cursor. I hate cursors, but sometimes (like this one), they're necessary.
Problem #3 - How to actually do a dynamic query and get a result back
This is actually trickier than it looks. You can't do a raw EXEC() for a return value, nor can you simply have the cmd you're executing populating a variable - because EXEC (and SP_ExecuteSql operate in a different context, so they can't populate variables outside in your script.)
You need to use SP_ExecuteSQL, but specify a return variable getting populated by the interior sql command. For example:
declare #sqlCmd nvarchar(max)
declare #dynamicReturn varchar(max)
set #sqlCmd = 'select #retVal=1'
EXEC Sp_executesql #sqlCmd,
N'#retVal varchar(max) output',
#dynamicReturn output
select #dynamicReturn
Problem #4 - How to write your Dynamic command
Here's where things get dicey, since it's where we're using a dynamic SQL command. The important thing here is: you cannot use anything the user provided as an input. Which means, you can't use the variables #tableNameFragment or #columnNameFragment. You can use the values in the #entityMatches table, though. Why? Because the user didn't populate them. They got populated by the data in the sys tables - it doesn't matter if the user puts something nefarious in the input variables, that #entityMatches data simply holds the existing table/column names that match.
Also important: When you're working on code that could be a problem if a future dev down the line tweaks or copies/pastes - you should put comment warnings to illuminate the issue.
So, putting it all together? You'll have something that looks like this:
declare #tableNameFragment varchar(100) -- note: these top 4 lines will eventually
declare #columnNameFragment varchar(100) -- be changed to stored proc args
set #tableNameFragment = 'Performance' -- and populated by the user calling
set #columnNameFragment = 'status' -- the proc (instead of hard-coded.)
declare #entityMatches TABLE (TableName varchar(200), ColName varchar(128))
insert into #entityMatches
Select sch.TABLE_SCHEMA + '.' + sysobj.name as TableName, syscol.name as ColName
From sysobjects sysobj
Join syscolumns syscol On sysobj.id = syscol.id
Join INFORMATION_SCHEMA.TABLES sch on sch.TABLE_NAME = sysobj.name
where sysobj.xtype = 'U'
and (sysobj.name like '%' + isnull(#tableNameFragment,'') + '%')
and (syscol.name like '%' + isnull(#columnNameFragment,'') + '%')
declare #returnResults TABLE (TableName varchar(200), ColName varchar(128), FirstValue varchar(max))
declare Cur Cursor For select TableName,ColName from #entityMatches
declare #cursorTable varchar(200), #cursorColumn varchar(128)
open Cur
fetch Next from cur into #cursorTable,#cursorColumn
while ##FETCH_STATUS = 0
begin
-- Note: the variables #cursorTable, #cursorColumn are NOT user populated
-- but instead are populated from the Sys tables. Because of this,
-- this dynamic sql below is not SQL-Injection vulnerable (the entries
-- are not populated from user entry of any sort.)
-- Be very careful modifying the lines below to make sure you don't
-- introduce a vulnerability.
declare #sqlCmd nvarchar(max)
declare #dynamicReturn varchar(max)
set #sqlCmd = 'select top 1 #retVal=[' + #cursorColumn + '] from ' + #cursorTable
EXEC Sp_executesql #sqlCmd,
N'#retVal varchar(max) output',
#dynamicReturn output
insert into #returnResults values (#cursorTable, #cursorColumn, #dynamicReturn)
fetch Next from cur into #cursorTable,#cursorColumn
End
close cur
deallocate cur
select * from #returnResults
Create a stored procedure like below mention stored procedure.
Get the table and column name from sysobject & syscolumn and add it in hash table on the base of parameter of stored procedure. After that declare a cursor and in loop of cursor create a dynamic query of column and table name and get first row of current column from table of cursor loop. After that execute the query and update the result in the hash table. At the end of lookup select the Record from hash table. Check the below stored procedure. I hope that its helpful for you.
Create procedure Sp_GetSampleData
#TName varchar(200) = ''
as
Select
a.name TableName, b.name ColumnName,
CAST('' as varchar(max)) as SampleValue
into
#Tbl
from
sysobjects a
inner join
syscolumns b on a.id = b.id
where
(#TName='' or a.name = #TName)
order ny
a.name, b.name
declare #TableName varchar(200), #ColumnName varchar(200),
#sqlText nvarchar(max), #Val varchar(max)
declare Cur Cursor For
select TableName, ColumnName
from #Tbl
open Cur
fetch Next from cur into #TableName,#ColumnName
while ##FETCH_STATUS =0
begin
set #sqlText=''
set #Val=''
SET #sqlText = N'SELECT Top 1 #Val=[' + #ColumnName + '] FROM testndb.dbo.' + #TableName
EXEC Sp_executesql
#sqlText,
N'#Val varchar(max) output',
#Val output
print #sqlText
update #Tbl set SampleValue=#Val where TableName=#TableName and ColumnName =#ColumnName
fetch Next from cur into #TableName,#ColumnName
End
close cur
deallocate cur
select * from #Tbl
SELECT field1 as "#q1_12"
FROM table1
FOR XML PATH ('qt'),TYPE
I need to shape the result of this query in xml, but the field's alias "#q1_12" should change according to different and complicated conditions.
So, is it possible to put the name of this attribute in a variable and use it in the query (like a sort of dynamic sql)
Is there some trick or workaround?
SELECT field1 as #attributename <-- this is a variable
FROM table1
FOR XML PATH ('qt'),TYPE
i know that, here the character '#' makes confusion, it has different meaning of treated either as variable or as literal(in this example it will be used by "XML FOR" command to put the field’s value into an attribute instead of an element)
Moreover this query is used as subquery, so using CASE/IF is very difficult, any ideas?
Your question shows clearly, that you know about dynamic SQL and your got some hints about REPLACE() to solve this on string level.
suggestion 1
But there might be an approach, especially if the possible names are all known and the list is not to big:
DECLARE #mockupTable TABLE(ID INT IDENTITY,SomeValue VARCHAR(100));
INSERT INTO #mockupTable VALUES('Row 1'),('Row 2');
--As you know, it is impossible to use a variable column alias...
--You can use a dynamic statement, but this involves some major draw-backs... (no ad-hoc, difficult to proceed with the output...)
--But you might try this
--To simulate your complex conditions I just check for one single value (but you'll get the ghist):
DECLARE #SomeCondition INT=2;
SELECT CASE WHEN #SomeCondition=1 THEN SomeValue END AS [#q1_01]
,CASE WHEN #SomeCondition=2 THEN SomeValue END AS [#q1_02]
,CASE WHEN #SomeCondition=3 THEN SomeValue END AS [#q1_03]
--add all possible attribute names here...
FROM #mockupTable t
FOR XML PATH('qt'),TYPE;
The magic behind: If not specified explicitly (Read about ELEMENTS XSINIL) the XML created with FOR XML PATH() will omit NULL values.
You can state all possible column names and all of them will be omitted, if the condition is not reached.
suggestion 2
this is the opposite approach:
DECLARE #KeepThis VARCHAR(100)='q1_02';
SELECT
(
SELECT SomeValue AS [#q1_01]
,SomeValue AS [#q1_02]
,SomeValue AS [#q1_03]
FROM #mockupTable t
FOR XML PATH('qt'),TYPE
).query('for $qt in /qt
return
<qt>
{
for $a in $qt/#*[local-name(.)=sql:variable("#KeepThis")]
return $a
}
</qt>');
The XML will be created with all attributes side-by-side, while the XQuery will return just the one we want to keep.
The only way is with dynamic SQL. You cannot use a variable to replace the value of a object, alias, etc. Therefore you will need to safely inject it into the statement:
DECLARE #attributename sysname = N'#q1_12';
DECLARE #SQL nvarchar(MAX);
SET #SQL = N'SELECT field1 AS ' + QUOTENAME(#attributename) + NCHAR(13) + NCHAR(10) +
N'FROM table1' + NCHAR(13) + NCHAR(10) +
N'FOR XML PATH ('qt'),TYPE;';
EXEC sp_executesql #SQL;
I ask for your help because I can not determine a solution to this problem. I am using SQL Server 2014.
I have a query like this:
SELECT
event_id,
CONCAT ('week', CHARINDEX('Y', weeks), '_date') AS num
FROM
CT_EVENT
which returns a week number of the form: week1_date
This value is the name of a column in another table. What I would like to do is a subrequest to get the value that is in it.
SELECT week1_date
FROM CT_CONFIG
I looked for a way to 'caster' a string in column name without actually finding.
Thank you in advance, and remain available for any supplement.
Jérémy
You cannot use Sub-query here, you will need to use Dynamic sql, something like this...
Declare #ColumnName SYSNAME
, #Sql NVARCHAR(MAX);
SELECT #ColumnName = CONCAT ('week', CHARINDEX ('Y', weeks), '_date')
FROM CT_EVENT
SET #Sql = N' SELECT ' + QUOTENAME(#ColumnName) + N' FROM CT_CONFIG;'
Exec sp_executesql #Sql;
The year is 2010.
SQL Server licenses are not cheap.
And yet, this error still does not indicate the row or the column or the value that produced the problem. Hell, it can't even tell you whether it was "string" or "binary" data.
Am I missing something?
A quick-and-dirty way of fixing these is to select the rows into a new physical table like so:
SELECT * INTO dbo.MyNewTable FROM <the rest of the offending query goes here>
...and then compare the schema of this table to the schema of the table into which the INSERT was previously going - and look for the larger column(s).
I realize that this is an old one. Here's a small piece of code that I use that helps.
What this does, is returns a table of the max lengths in the table you're trying to select from. You can then compare the field lengths to the max returned for each column and figure out which ones are causing the issue. Then it's just a simple query to clean up the data or exclude it.
DECLARE #col NVARCHAR(50)
DECLARE #sql NVARCHAR(MAX);
CREATE TABLE ##temp (colname nvarchar(50), maxVal int)
DECLARE oloop CURSOR FOR
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'SOURCETABLENAME' AND TABLE_SCHEMA='dbo'
OPEN oLoop
FETCH NEXT FROM oloop INTO #col;
WHILE (##FETCH_STATUS = 0)
BEGIN
SET #sql = '
DECLARE #val INT;
SELECT #val = MAX(LEN(' + #col + ')) FROM dbo.SOURCETABLENAME;
INSERT INTO ##temp
( colname, maxVal )
VALUES ( N''' + #col + ''', -- colname - nvarchar(50)
#val -- maxVal - int
)';
EXEC(#sql);
FETCH NEXT FROM oloop INTO #col;
END
CLOSE oloop;
DEALLOCATE oloop
SELECT * FROM ##temp
DROP TABLE ##temp;
Another way here is to use binary search.
Comment half of the columns in your code and try again. If the error persists, comment out another half of that half and try again. You will narrow down your search to just two columns in the end.
You could check the length of each inserted value with an if condition, and if the value needs more width than the current column width, truncate the value and throw a custom error.
That should work if you just need to identify which is the field causing the problem. I don't know if there's any better way to do this though.
Recommend you vote for the enhancement request on Microsoft's site. It's been active for 6 years now so who knows if Microsoft will ever do anything about it, but at least you can be a squeaky wheel: Microsoft Connect
For string truncation, I came up with the following solution to find the max lengths of all of the columns:
1) Select all of the data to a temporary table (supply column names where needed), e.g.
SELECT col1
,col2
,col3_4 = col3 + '-' + col4
INTO #temp;
2) Run the following SQL Statement in the same connection (adjust the temporary table name if needed):
DECLARE #table VARCHAR(MAX) = '#temp'; -- change this to your temp table name
DECLARE #select VARCHAR(MAX) = '';
DECLARE #prefix VARCHAR(256) = 'MAX(LEN(';
DECLARE #suffix VARCHAR(256) = ')) AS max_';
DECLARE #nl CHAR(2) = CHAR(13) + CHAR(10);
SELECT #select = #select + #prefix + name + #suffix + name + #nl + ','
FROM tempdb.sys.columns
WHERE object_id = object_id('tempdb..' + #table);
SELECT #select = 'SELECT ' + #select + '0' + #nl + 'FROM ' + #table
EXEC(#select);
It will return a result set with the column names prefixed with 'max_' and show the max length of each column.
Once you identify the faulty column you can run other select statements to find extra long rows and adjust your code/data as needed.
I can't think of a good way really.
I once spent a lot of time debugging a very informative "Division by zero" message.
Usually you comment out various pieces of output code to find the one causing problems.
Then you take this piece you found and make it return a value that indicates there's a problem instead of the actual value (in your case, should be replacing the string output with the len(of the output)). Then manually compare to the lenght of the column you're inserting it into.
from the line number in the error message, you should be able to identify the insert query that is causing the error. modify that into a select query to include AND LEN(your_expression_or_column_here) > CONSTANT_COL_INT_LEN for the string various columns in your query. look at the output and it will give your the bad rows.
Technically, there isn't a row to point to because SQL didn't write the data to the table. I typically just capture the trace, run it Query Analyzer (unless the problem is already obvious from the trace, which it may be in this case), and quickly debug from there with the ages old "modify my UPDATE to a SELECT" method. Doesn't it really just break down to one of two things:
a) Your column definition is wrong, and the width needs to be changed
b) Your column definition is right, and the app needs to be more defensive
?
The best thing that worked for me was to put the rows first into a temporary table using select .... into #temptable
Then I took the max length of each column in that temp table. eg. select max(len(jobid)) as Jobid, ....
and then compared that to the source table field definition.
I want to run the following SELECT query:
DECLARE #ColumnName nvarchar(50)
SET #ColumnName = 'AlarmID' -- actually these are calculated
-- by another SELECT but this isn't relevant
SELECT MIN(#ColumnName) FROM INSERTED
This doesn't work, it returns the value of #ColumnName instead of the actual data. How can I make this work?
I cannot put the SELECT into a string and run it with sp_executesql because I will lose access to the INSERTED table (this is running in a trigger).
EXEC('SELECT MIN(' + #ColumnName + ') FROM INSERTED')
Derived from the link smoore provided.
Use this if you want the minimum as a variable:
SELECT #columnName = MIN(#ColumnName) FROM YourTable
You can't really do that. Your best bet, depending on number of possible values of #ColumnName, is to dynamically set the field value with a case statement, or selectively run the right query using an IF statement:
SELECT CASE #ColumnName WHEN 'AlarmID' THEN MIN(AlarmID) WHEN 'AnotherField' THEN
MIN(AnotherField) END AS MinimumValue FROM INSERTED
OR
IF #ColumnName = 'AlarmID'
SELECT MIN(AlarmID) FROM INSERTED
ELSE
....