I am trying to write a sub-query, that stores all the results in a single column separated by a comma. My code looks something like this
SELECT column1,
column2,
CourseRequests=(SELECT INNERCourseRequests =
COALESCE(CASE
WHEN innercourserequests
= '' THEN
crse_name
ELSE innercourserequests
+ ',' +
crse_name
END, '')
FROM tor_studentcrserequest SCR
WHERE SCR.stud_pk = MS.tt_stud_pk
AND SCR.delt_flag = 0),
column4
FROM tbl_mainstudent MS
When I try to execute the stored procedure, I get an error saying Invalid column name 'INNERCourseRequests'.
What is the correct way to do this?
TSR is a reference to table from the outer column
EDIT: I changed it to:
CourseRequests=(SELECT INNERCourseRequests =
COALESCE(case when #INNERCourseRequests='' THEN CRSE_NAME ELSE
#INNERCourseRequests+','+CRSE_NAME end,'')
However, now I"m getting an error saying subquery returned more than 1 result which is expected.
You can use FOR XML along with a few REPLACEs as shown here:
SELECT column1,
column2,
CourseRequests=COALESCE(
REPLACE(REPLACE(REPLACE((
SELECT crse_name
FROM (
SELECT 1, 22, 'first', 0
UNION ALL SELECT 2, 22, 'second', 1
UNION ALL SELECT 3, 22, 'third', 0
UNION ALL SELECT 4, 555, 'first', 1
) SCR (id, stud_pk, crse_name, delt_flag)
WHERE SCR.stud_pk = MS.tt_stud_pk
AND SCR.delt_flag = 0
FOR XML PATH('')
),'</crse_name><crse_name>', ','),
'</crse_name>', ''), -- remove end tag
'<crse_name>', ''), -- remove beginning tag
''), -- optional COALESCE to ensure no NULLs
column4
FROM (
SELECT 1, 'a', 'b', '2014-01-01'
UNION ALL SELECT 22, 'd', 'e', '2014-02-02'
) MS (tt_stud_pk, column1, column2, column4)
Output:
column1 column2 CourseRequests column4
a b 2014-01-01
d e first,third 2014-02-02
Explanation:
The FOR XML PATH('') flattens the result of the sub-query to be:
<crse_name>first</crse_name><crse_name>third</crse_name>
The first REPLACE converts just the end-tag/beginning-tag combinations that are only found between values (i.e. where the commas go)
The second REPLACE removes the ending tag (can't be done before the first REPLACE)
The third REPLACE removes the beginning tag (can't be done before the first REPLACE)
Note:
There might be a slightly more elegant way to do the XML stuff so you don't need all of the REPLACEs, but not sure and this does work.
I'm pretty sure you can't do this with a single query, and I'm not entirely certain the tactic I've
come up with is a legitimate tactic--meaning, if it is undocumented, a future version of SQL might
not support this. With that said:
Start with the following:
DECLARE #List varchar(max)
SELECT #List = isnull(#List + ', ', '') + InnerCourseRequests
from tor_studentcrserequest
where stud_pk = <TestValue>
and delt_flag = 0
PRINT #List
This will generate a comma-delimited list of all InnerCourseRequests from the tor_studentcrserequest table for a single stud_pk.
Next, turn it into a function:
DROP FUNCTION phkTest
GO
CREATE FUNCTION phkTest (#stud_pk int) -- Change datatype, if not int
RETURNS varchar(max)
AS
BEGIN
DECLARE #List varchar(max)
SELECT #List = isnull(#List + ', ', '') + InnerCourseRequests
from tor_studentcrserequest
where stud_pk = #stud_pk
and delt_flag = 0
RETURN #List
END
GO
(Add a second parameter for delt_flag, if that might vary somehow)
And add that to a query:
SELECT distinct tt_stud_pk, dbo.phkTest(stud_pk)
from tbl_mainstudent
(I wrote all this using one of my tables, then cut-and-paste your table/columns in, so there may be some syntax issues to deal with.)
There may be ways to improve performance for big tables (OUTER APPLY, select distinct before calling the function, and so forth), and it's entirly likely that this might best be done via procedural code by whatever's querying the data in the first place.
Related
Below are two queries, I'm trying to workout the following questions while on my sql learning quest.
Query 1 - What would this be called in sql?
I have tried to Web search for usage examples but do not know what to search on.
Query 1 and 2 give me the same result, are they different?
Execution Plans look similar except last one has one extra step:
UDX Cost 0% step
Which would be preferred for putting row into a string?
Are these the only ways in sql to put rows into one string
Many thanks.
-- Query 1
declare #string varchar(max)
select #string = coalesce(#string, '') + coalesce(col1, '')
from
(
select '1' as col1
Union
select '2' as col1
Union
select '3' as col1
Union
select '4' as col1
) x
select #string;
-- Query 2
with cte_string
as
(
select '1' as col1
union
select '2' as col1
Union
select '3' as col1
Union
select '4' as col1
)
select cast(col1 as nvarchar(1))
from cte_string
for xml path(''), type;
As you've noticed there are multiple ways of concatenating strings in SQL. I'm not certain there are specific names for these used methods. I would describe it as "I'm concatenating strings using ...".
Preferred really depends on your personal taste more than anything else. For example my preferred method would be using STRING_AGG even if it is slower because of the reasons below. So do yourself a favor and update your SQL server.
It's a built-in function. Updates in later versions could make it faster without you having to change any code.
The intent is much clearer than other methods. If someone else browses your SQL there is no question about what you're trying to accomplish.
Easier to expand. Unless you know your way around SQL, if you wanted to add a comma or any other separator between every single value, you'd have to think what you'd have to change to accomplish this. With STRING_AGG it's just a matter of changing a parameter. Using a GROUP BY on a query with STRING_AGG is trivial. On the other methods, not so much.
Concatenating using COALESCE:
DECLARE #string VARCHAR(MAX)
SELECT #string = COALESCE(#string, '') + COALESCE([value], '')
FROM (
SELECT '1' AS [value]
UNION
SELECT '2'
) AS [t]
SELECT #string
Concatenating using FOR XML PATH:
DECLARE #string VARCHAR(MAX)
SELECT #string = (
SELECT '' + [value]
FROM (
SELECT '1' AS [value]
UNION
SELECT '2'
) AS [t]
FOR XML PATH(''))
SELECT #string
Concatenating using common table expression.
WITH cte_string AS
(
SELECT '1' AS [value]
UNION
SELECT '2'
)
SELECT '' + [value]
FROM cte_string
FOR XML PATH('')
Concatenating using the new STRING_AGG function in SQL 2017:
DECLARE #string VARCHAR(MAX)
SELECT #string = STRING_AGG([value], '')
FROM (
SELECT '1' AS [value]
UNION
SELECT '2'
) AS [t]
SELECT #string
I have a test table like this-
Field
A
B
C
END
D
E
F
END
G
H
I
END
I want to compress this data on key word "END" in this format-
Field
A|B|C
D|E|F
G|H|I
Tried using Monarch Pro but could not get the desired results. I really can't think of a way to start on this in SQL. Please help.
This might help.
DECLARE #WORD VARCHAR(300)
SELECT #WORD = COALESCE(#WORD + '|','') + Field FROM [YourTable]
SELECT #WORD = REPLACE(#WORD, 'END', '$')
SELECT #WORD Field INTO #A
;WITH c(FieldOutput, Field) as (
select CAST(LEFT(Field, CHARINDEX('$',Field+'$')-2) AS VARCHAR(100)),
STUFF(Field, 1, CHARINDEX('$',Field+'$')+1, '')
from #A
where ISNULL(Field, '') <> ''
union all
select CAST(LEFT(Field, CHARINDEX('$',Field+'$')-2) AS VARCHAR(100)),
STUFF(Field, 1, CHARINDEX('$',Field+'$')+1, '')
from c
where ISNULL(Field, '') <> ''
)
select FieldOutput AS Field
from c
EDIT
Created a fiddle to test this out.
High level approach:
Use a cursor to step through the table row by row.
Append to a temporary variable until the row reads 'END' then write the contents of the temp variable to a row on a different table.
Iterate through until you reach the end of the table.
There is probably a more elegant, non-cursor based way of doing this, but this will get the job done.
I have data that has come over from a hierarchical database, and it often has columns that contain data that SHOULD be in another table, if the original database had been relational.
The column's data is formatted in pairs, with LABEL\VALUE with a space as the delimiter, like this:
LABEL1\VALUE LABEL2\VALUE LABEL3\VALUE
There is seldom more than one pair in a record, but there as many as three. There are 24 different possible Labels. There are other columns in this table, including the ID. I have been able to convert this column into a sparse array without using a cursor, with columns for ID, LABEL1, LABEL2, etc....
But this is not ideal for using in another query. My other option it to use a cursor, loop through the entire table once and write to a temp table, but I can't see to get it to work the way I want. I have been able to do it in just a few minutes in VB.NET, using a couple of nested loops, but can't manage to do it in T-SQL even using cursors. Problem is, that I would have to remember to run this program every time before I want to use the table it creates. Not ideal.
So, I read a row, split out the pairs from 'LABEL1\VALUE LABEL2\VALUE LABEL3\VALUE' into an array, then split them out again, then write the rows
ID, LABEL1, VALUE
ID, LABEL2, VALUE
ID, LABEL3, VALUE
etc...
I realize that 'splitting' the strings here is the hard part for SQL to do, but it just seems a lot more difficult that it needs to be. What am I missing?
Assuming that the data label contains no . characters, you can use a simple function for this:
CREATE FUNCTION [dbo].[SplitGriswold]
(
#List NVARCHAR(MAX),
#Delim1 NCHAR(1),
#Delim2 NCHAR(1)
)
RETURNS TABLE
AS
RETURN
(
SELECT
Val1 = PARSENAME(Value,2),
Val2 = PARSENAME(Value,1)
FROM
(
SELECT REPLACE(Value, #Delim2, '.') FROM
(
SELECT LTRIM(RTRIM(SUBSTRING(#List, [Number],
CHARINDEX(#Delim1, #List + #Delim1, [Number]) - [Number])))
FROM (SELECT Number = ROW_NUMBER() OVER (ORDER BY name)
FROM sys.all_objects) AS x
WHERE Number <= LEN(#List)
AND SUBSTRING(#Delim1 + #List, [Number], LEN(#Delim1)) = #Delim1
) AS y(Value)
) AS z(Value)
);
GO
Sample usage:
DECLARE #x TABLE(ID INT, string VARCHAR(255));
INSERT #x VALUES
(1, 'LABEL1\VALUE LABEL2\VALUE LABEL3\VALUE'),
(2, 'LABEL1\VALUE2 LABEL2\VALUE2');
SELECT x.ID, t.val1, t.val2
FROM #x AS x CROSS APPLY
dbo.SplitGriswold(REPLACE(x.string, ' ', N'ŏ'), N'ŏ', '\') AS t;
(I used a Unicode character unlikely to appear in data above, only because a space can be problematic for things like length checks. If this character is likely to appear, choose a different one.)
Results:
ID val1 val2
-- -------- --------
1 LABEL1 VALUE
1 LABEL2 VALUE
1 LABEL3 VALUE
2 LABEL1 VALUE2
2 LABEL2 VALUE2
If your data might have ., then you can just make the query a little more complex, without changing the function, by adding yet another character to the mix that is unlikely or impossible to be in the data:
DECLARE #x TABLE(ID INT, string VARCHAR(255));
INSERT #x VALUES
(1, 'LABEL1\VALUE.A LABEL2\VALUE.B LABEL3\VALUE.C'),
(2, 'LABEL1\VALUE2.A LABEL2.1\VALUE2.B');
SELECT x.ID, val1 = REPLACE(t.val1, N'ű', '.'), val2 = REPLACE(t.val2, N'ű', '.')
FROM #x AS x CROSS APPLY
dbo.SplitGriswold(REPLACE(REPLACE(x.string, ' ', 'ŏ'), '.', N'ű'), 'ŏ', '\') AS t;
Results:
ID val1 val2
-- -------- --------
1 LABEL1 VALUE.A
1 LABEL2 VALUE.B
1 LABEL3 VALUE.C
2 LABEL1 VALUE2.A
2 LABEL2.1 VALUE2.B
With only three values, you can manage to do this by brute force:
select (case when rest like '% %'
then left(rest, charindex(' ', rest) - 1)
else rest
end) as val2,
(case when rest like '% %'
then substring(col, charindex(' ', col) + 1, 1000)
end) as val3
from (select (case when col like '% %'
then left(col, charindex(' ', col) - 1)
else col
end) as val1,
(case when col like '% %'
then substring(col, charindex(' ', col) + 1, 1000)
end) as rest
from t
) t
Using the SQL split string function given at referenced SQL tutorial, you can split the label-value pairs as following
SELECT
id, max(label) as label, max(value) as value
FROM (
SELECT
s.id,
label = case when t.id = 1 then t.val else NULL end,
value = case when t.id = 2 then t.val else NULL end
FROM dbo.Split(N'LABEL1\VALUE1 LABEL2\VALUE2 LABEL3\VALUE3', ' ') s
CROSS APPLY dbo.Split(s.val, '\') t
) t
group by id
You can see that the split string function is called twice, first for splitting pairs from others. Then the second split function joined to previous one using CROSS APPLY splits labels from pairs
I replace all blanks with # using this
SELECT *, REPLACE(NAME,' ','#') AS NAME2
which results miss#test#blogs############## (different number of #s dependent on length of name!
I then delete all # signs after the name using this
select *, substring(Name2,0,charindex('##',Name2)) as name3
which then gives my desired results of, for example MISS#test#blogs
However some wheren't giving this result, they are null. This is because annoyingly some rows in the sheet I have read in dont have the spaces after the name.
is there a case statement i can use so it only deletes # signs after the name if they are there in the first place?
Thanks
The function rtrim can be used to remove trailing spaces. For example:
select replace(rtrim('miss test blogs '),' ','#')
-->
'miss#test#blogs'
Example at SQL Fiddle.
try this:
Declare #t table (name varchar(100),title varchar(100),forename varchar(100))
insert into #t
values('a b c','dasdh dsalkdk asdhl','asd dfg sd')
SELECT REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(name)),' ',' '+CHAR(7)),CHAR(7)+' ','') ,CHAR(7),'') AS Name,
REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(title)),' ',' '+CHAR(7)),CHAR(7)+' ','') ,CHAR(7),'') AS title,
REPLACE(REPLACE(REPLACE(LTRIM(RTRIM(forename)),' ',' '+CHAR(7)),CHAR(7)+' ','') ,CHAR(7),'') AS forename
FROM #t WHERE
(CHARINDEX(' ',NAME) > 0 or CHARINDEX(' ',title) > 0 or CHARINDEX(' ',forename) > 0)
SQL Fiddle Demo
select name2, left(name2,len(name2)+1-patindex('%[^#]%',reverse(name2)+'.'))
from (
SELECT *, REPLACE(NAME,' ','#') AS NAME2
from t
) x;
Check this SQL Fiddle
For posterity, sample table:
create table t (name varchar(100));
insert t select 'name#name#ne###'
union all select '#name#name'
union all select 'name name hi '
union all select 'joe public'
union all select ''
union all select 'joe'
union all select 'joe '
union all select null
union all select ' leading spaces'
union all select ' leading trailing ';
Don't quite understand the question, but if the problem is there is not spaces after some names, can't you do this first:
SELECT *, REPLACE(NAME+' ',' ','#') AS NAME2
i.e., add a space to all names right off the bat?
I had this same problem some days ago.
Well actually, there's a quickly way to subtract the spaces from both the begin and end inside strings. In SQL Server, you can use the RTRIM and LTRIM for this. The first one supresses spaces from right side and the second supresses from left. But, if in your scenario also may exists more than one space in the middle of the string I sugest you take a look on this post on SQL Server Central: http://www.sqlservercentral.com/articles/T-SQL/68378/
There the script's author explain, in details, a good solution for this situation.
I have table where in a table called test which have 4 fields.one field named as listing, I have 1,2,3,4,5,6 multiple values separated by comma, I need to check whether in that table and in that particular field an id say 4 is there or not.. by a sql query.
You database design is wrong, that's why you have problems querying the data. You should have the values in a separate table, so that teach value is in it's own field. Then it would be easy to find the records:
select t.testId
from test t
inner join listing l on l.testId = t.testId
where l.id = 4
Now you have to use some ugly string comparison to find the records:
select testId
from test
where ','+listing+',' like '%,4,%'
You can try
SELECT *
FROM YourTable
WHERE REPLACE(Col, ' ', '') LIKE '4,%' --Starts with
OR REPLACE(Col, ' ', '') LIKE '%,4' --Ends with
OR REPLACE(Col, ' ', '') LIKE '%,4,%' --Contains
OR REPLACE(Col, ' ', '') = '4' --Equals
Just as a matter of interest, have a look at this
DECLARE #delimiter NVARCHAR(5),
#Val INT
SELECT #Val = 40
SELECT #delimiter = ','
DECLARE #YourTable TABLE(
ID INT,
Vals VARCHAR(50)
)
INSERT INTO #YourTable (ID,Vals) SELECT 1, '1,2,3,4,5,6,7,8'
DECLARE #TempTable TABLE(
ID INT,
Vals XML
)
INSERT INTO #TempTable
SELECT ID,
CAST('<d>' + REPLACE(Vals, #delimiter, '</d><d>') + '</d>' AS XML)
FROM #YourTable
SELECT *
FROM #TempTable tt
WHERE EXISTS(
SELECT T.split.value('.', 'nvarchar(max)') AS data
FROM tt.Vals.nodes('/d') T(split)
WHERE T.split.value('.', 'nvarchar(max)') = #Val
)
The common approach is to parse the list into a table variable or table-valued function, then either join against the table, or use an EXISTS sub-query.
There are lots of examples on how to do this:
http://www.bing.com/search?setmkt=en-US&q=SQL+parse+list+into+table
You could use an instring function in the where clause and in the select clause:
Oracle:
select substr(column, instr(column, '1', 1), 1)
where instr(column, '1', 1) > 0
works if you want a single value. Alternatively you can use a combination of case or decode statements to create a single column for each possible value:
select
decode(instr(column, '1', 1), 0, substr(column, instr(column, '1', 1), 1), null) c1,
decode(instr(column, '2', 1), 0, substr(column, instr(column, '2', 1), 1), null) c2,
decode(instr(column, '3', 1), 0, substr(column, instr(column, '3', 1), 1), null) c3
The beauty of this approach for such a poorly normalised set of data is you can save this as a view and then run SQL on that, so if you save the above you could use:
select c1, c2 from view where c1 is not null or c2 is not null
NB. In other dbms you might have to use different syntax, possibly the case rather decode statement
If you need to find 4 and only 4 (ie not 14 or 24 or 40 etc) you should use
SELECT * FROM foo WHERE col LIKE '%, 4,%'
or
SELECT * FROM foo WHERE col LIKE '%,4,%'
if there are no spaces between the commas and numbers
How about this?
Select * From Foo Where Col like '%4%'