How to loop through a select statement results to have a formatted text?
for example the select is like:
select name from table
and we want a variable #names like this:
"name1,name2,name3"
Database is SQL Server 2005
If table contains several records, i.e.:
1, name1, ..
2, name2, ..
3, name3, ..
then this query:
DECLARE #names VARCHAR(MAX)
SELECT #names = COALESCE(#names + ', ', '') + name
FROM table
will produce next result:
name1, name2, name3
See COALESCE on MSDN
This would need to be done within a function. There's no quick way to do this. Normally you would do this within your client side code.
If you plan on making a function that you do on each row form another query it will be really slow, because the function needs to be called for each row. You should work with sets of data at one time, it does all rows at one time. This is an example of how to concatenate multiple values for each row of another query:
set nocount on;
declare #t table (id int, name varchar(20), x char(1))
insert into #t (id, name, x)
select 1,'test1', 'a' union
select 1,'test1', 'b' union
select 1,'test1', 'c' union
select 2,'test2', 'a' union
select 2,'test2', 'c' union
select 3,'test3', 'b' union
select 3,'test3', 'c'
SELECT p1.id, p1.name,
stuff(
(SELECT
', ' + x
FROM #t p2
WHERE p2.id=p1.id
ORDER BY name, x
FOR XML PATH('')
)
,1,2, ''
) AS p3
FROM #t p1
GROUP BY
id, name
OUTPUT:
id name p3
----------- -------------------- -----------
1 test1 a, b, c
2 test2 a, c
3 test3 b, c
Related
Currently data is like below:-
SQL Query:-
declare #t table
(
Id int,
ReportedDate DATE,
[Name] varchar(10)
)
insert into #t
select 1,'2016-01-01','ab' union all
select 2,'2016-01-01','a' union all
select 1,'2016-01-20','hha' union all
select 2,'2016-01-20','jnsjja' union all
select 1,'2016-01-01','jsjb' union all
select 2,'2016-01-01','sjjjwb' union all
select 1,'2016-01-20','bjd' union all
select 2,'2016-01-20','bwjw'
select * from #t order by id, ReportedDate
Expected Result:-
It's working in case we have only 2 columns, one is Id & other one is anything. But for mutiple column I am unable
There are countless examples for this, however, since you have a nicely structured question which is easy to copy and paste....
Select A.ID
,A.ReportedDate
,Name = Stuff((Select Distinct ',' +Name From #t Where ID=A.ID and ReportedDate=A.ReportedDate For XML Path ('')),1,1,'')
From (Select Distinct ID,ReportedDate From #t ) A
Returns
ID ReportedDate Name
1 2016-01-01 ab,jsjb
1 2016-01-20 bjd,hha
2 2016-01-01 a,sjjjwb
2 2016-01-20 bwjw,jnsjja
I have a table with name "PrintWord" and column name as col_letter and data in it is as follows:
"col_letter"
S
A
C
H
I
N
I would like to print the o/p from this table as:
SACHIN
Thanks!
DECLARE #t table
(
Name varchar(10)
)
INSERT INTO #t
SELECT 's' UNION ALL
SELECT 'a' UNION ALL
SELECT 'c' UNION ALL
SELECT 'h' UNION ALL
SELECT 'i' UNION ALL
SELECT 'n'
SELECT DISTINCT
stuff(
(
SELECT ' '+ [Name] FROM #t FOR XML PATH('')
),1,1,'')
FROM (SELECT DISTINCT Name FROM #t ) t
There is a hard-coded version :
SELECT col_letter
FROM PrintWord
ORDER BY
CASE col_letter
WHEN 'S' THEN 1
WHEN 'A' THEN 2
WHEN 'C' THEN 3
WHEN 'H' THEN 4
WHEN 'I' THEN 5
WHEN 'N' THEN 6
END
FOR XML PATH('')
You need an ORDER BY clause to guarantee the order of the letters.
I gained some help from this question, but still need some further assistance.
I need to be able to generate the next available 2-digit alphanumeric code. I cannot change the table definition, before you ask. I am working in T-SQL.
So, for example, let's say I have the sequence
00, 01, 02,..., 09, 0A, 0B, 0C,..., 0Y, 0Z, 10, 11,...1Y, 1Z, 20, 21,..., 9Y, 9Z, I would like for the next id to be A0,
then A1, A2, ..., A9, AA, AB, AC, ..., AZ, I would like for the next id to be B0, then B1, etc.
So, in short, I would like to go from 00 all the way to ZZ and each time look for the MAX in that field and assign a new code 1 greater than the max. I would understand that A > 9, and the first column greater than the second, so A0 > 99 and AA > A9.
I wish I could just assign a numeric id to all of this, but the table definition is more critical at this point and so I'm not allowed to change it, so I am trying to maximize the available ids I'll have in such a limited space.
Thank you for your help.
Have a look at this. This is a really nasty problem for ID's. You've effectively limited yourself a low number of permutations of the key with 2 characters. Also you have a problem that you'll need to deal with if ZZ is used and this algorithm runs again. I have expanded these into as logical steps as possible for demonstration, but feel free to condense as needed.
DECLARE #ExistingTable TABLE (ID CHAR(2))
INSERT INTO #ExistingTable (ID) VALUES ('5A'),('5B')
DECLARE #NewID CHAR(2)
;WITH
Ranks AS (
SELECT '0' AS [Character] UNION SELECT '1' AS [Character] UNION SELECT '2' UNION SELECT '3' UNION SELECT '4' UNION SELECT '5' UNION SELECT '6' UNION
SELECT '7' UNION SELECT '8' UNION SELECT '9' UNION SELECT 'A' UNION SELECT 'B'UNION
SELECT 'C' UNION SELECT 'D' UNION SELECT 'E' UNION SELECT 'F' UNION SELECT 'G' UNION SELECT 'H' UNION
SELECT 'I' UNION SELECT 'J' UNION SELECT 'K' UNION SELECT 'L' UNION SELECT 'M' UNION SELECT 'N' UNION
SELECT 'O' UNION SELECT 'P' UNION SELECT 'Q' UNION SELECT 'R' UNION SELECT 'S' UNION SELECT 'T' UNION
SELECT 'U' UNION SELECT 'V' UNION SELECT 'W' UNION SELECT 'X' UNION SELECT 'Y' UNION SELECT 'Z'
), Permutations AS (
SELECT SecondChar.[Character] + FirstChar.[Character] AS PermuteID
FROM Ranks AS FirstChar
CROSS JOIN Ranks AS SecondChar
), PermutationsKeyed AS (
SELECT ROW_NUMBER() OVER (ORDER BY PermuteID ASC) AS PrimaryKeyHolder,
PermuteID
FROM Permutations
), MaxPK AS (
SELECT MAX(Perm.PrimaryKeyHolder) + 1 AS MaxPK
FROM #ExistingTable AS E
INNER JOIN PermutationsKeyed AS Perm ON (E.ID = Perm.PermuteID)
)
SELECT #NewID = Perm.PermuteID
FROM PermutationsKeyed AS Perm
INNER JOIN MaxPK AS M ON (Perm.PrimaryKeyHolder = M.MaxPK)
SELECT #NewID
I'm not sure how you wanted to go about returning the next value but I think this a simple and efficient ways to get all your values. Let me know if you need anything else.
DECLARE #values TABLE (val CHAR(1));
DECLARE #int INT = 48,
#letters INT = 65;
IF OBJECT_ID('dbo.tbl_keys') IS NOT NULL
DROP TABLE dbo.tbl_keys;
--This will hold the values so you can always reference them
CREATE TABLE dbo.tbl_Keys
(
--Primary key will create a clustered index on rank_id by default
rank_id INT PRIMARY KEY,
ID_Code CHAR(2)
);
--Another index on ID_Code
CREATE NONCLUSTERED INDEX idx_ID_Code ON tbl_keys(ID_Code);
--This is how I get all your individual values
WHILE (SELECT COUNT(*) FROM #values) < 36
BEGIN
IF(#int <= 57)
INSERT INTO #values VALUES(CHAR(#int));
INSERT INTO #values
VALUES (CHAR(#letters))
SET #int = #int + 1;
SET #letters = #letters + 1;
END
--Insert all possible combinations and rank them
INSERT INTO tbl_Keys
--ASCII is your best friend. It returns the ASCII code(numeric value) for characters
SELECT ROW_NUMBER() OVER (ORDER BY ASCII(A.val),ASCII(B.val)) AS rank_id,
A.val + B.val ID
FROM #values A
CROSS JOIN #values B;
I provide two different ways of getting the next ID_code(Read comments):
--Here's some dummy data
WITH CTE_DummyTable
AS
(
SELECT '00' ID_Code
UNION ALL
SELECT '01'
UNION ALL
SELECT '02'
)
----Here's how to get the next value with the assumption there are no gaps in your data
--SELECT MIN(ID_Code) next_id_code
--FROM tbl_Keys
--WHERE ID_code > (SELECT MAX(id_code) FROM CTE_DummyTable)
--This one doesn't assume the gaps and returns the lowest available ID_code
SELECT MIN(ID_Code) next_id_code
FROM tbl_Keys
WHERE ID_code NOT IN (SELECT DISTINCT id_code FROM CTE_DummyTable)
Note: If you were ever to want to convert your alphanumeric values really easily for whatever reason without changing the rank try this.
SELECT rank_id,
ID_code,
CAST(CONCAT(ASCII(LEFT(id_code,1)),ASCII(RIGHT(id_code,1))) AS INT) AS numeric_id_code
FROM tbl_Keys
Assuming I have the following table:
AAAAAA
AAAAAB
CCCCCC
How could I craft a query that would let me know that AAAAA and AAAAB are similar (as they share five characters in a row)? Ideally I would like to write this as a query that would check if the two fields shared five characters in a row anywhere in the string but this seems outside the scope of SQL and something I should write into a C# application?
Ideally the query would add another column that displays: Similar to 'AAAAA', 'AAAAB'
I suggest you do not try to violate 1NF by introducing a multi-valued attribute.
Noting that SUBSTRING is highly portable:
WITH T
AS
(
SELECT *
FROM (
VALUES ('AAAAAA'),
('AAAAAB'),
('CCCCCC')
) AS T (data_col)
)
SELECT T1.data_col,
T2.data_col AS data_col_similar_to
FROM T AS T1, T AS T2
WHERE T1.data_col < T2.data_col
AND SUBSTRING(T1.data_col, 1, 5)
= SUBSTRING(T2.data_col, 1, 5);
Alternativvely:
T1.data_col LIKE SUBSTRING(T2.data_col, 1, 5) + '%';
This will find all matches, also those in the middle of the word, it will not perform well on a big table
declare #t table(a varchar(20))
insert #t select 'AAAAAA'
insert #t select 'AAAAAB'
insert #t select 'CCCCCC'
insert #t select 'ABCCCCC'
insert #t select 'DDD'
declare #compare smallint = 5
;with cte as
(
select a, left(a, #compare) suba, 1 h
from #t
union all
select a, substring(a, h + 1, #compare), h+1
from cte where cte.h + #compare <= len(a)
)
select t.a, cte.a match from #t t
-- if you don't want the null matches, remove the 'left' from this join
left join cte on charindex(suba, t.a) > 0 and t.a <> cte.a
group by t.a, cte.a
Result:
a match
-------------------- ------
AAAAAA AAAAAB
AAAAAB AAAAAA
ABCCCCC CCCCCC
CCCCCC ABCCCCC
You can use left to compare the first five characters and you can use for xml path to concatenate the similar strings to one column.
declare #T table
(
ID int identity primary key,
Col varchar(10)
)
insert into #T values
('AAAAAA'),
('AAAAAB'),
('AAAAAC'),
('CCCCCC')
select Col,
stuff((select ','+T2.Col
from #T as T2
where left(T1.Col, 5) = left(T2.Col, 5) and
T1.ID <> T2.ID
for xml path(''), type).value('.', 'varchar(max)'), 1, 1, '') as Similar
from #T as T1
Result:
Col Similar
---------- -------------------------
AAAAAA AAAAAB,AAAAAC
AAAAAB AAAAAA,AAAAAC
AAAAAC AAAAAA,AAAAAB
CCCCCC NULL
Let´s say I have two tables, "Garden" and "Flowers". There is a 1:n-relationship between these tables, because in a garden can be many flowers. Is it possible to write an SQL query which returns a result with the following structure:
GardenName Flower1Name Flower2Name .... (as long as there are entries in flowers)
myGarden rose tulip
CREATE TABLE #Garden (Id INT, Name VARCHAR(20))
INSERT INTO #Garden
SELECT 1, 'myGarden' UNION ALL
SELECT 2, 'yourGarden'
CREATE TABLE #Flowers (GardenId INT, Flower VARCHAR(20))
INSERT INTO #Flowers
SELECT 1, 'rose' UNION ALL
SELECT 1, 'tulip' UNION ALL
SELECT 2, 'thistle'
DECLARE #ColList nvarchar(max)
SELECT #ColList = ISNULL(#ColList + ',','') + QUOTENAME('Flower' + CAST(ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS VARCHAR))
FROM #Flowers WHERE GardenId = (
SELECT TOP 1 GardenId
FROM #Flowers
ORDER BY COUNT(*) OVER (PARTITION BY GardenId) DESC
)
EXEC (N'
;WITH cte As
(
SELECT *, ''Flower'' + CAST(ROW_NUMBER() OVER (PARTITION BY GardenId ORDER BY (SELECT 0)) AS VARCHAR) RN
FROM #Flowers F
)
SELECT Name,' + #ColList + N'
FROM cte
JOIN #Garden g ON g.Id = GardenId
PIVOT (MAX(Flower) FOR RN IN (' + #ColList + N')) Pvt')
DROP TABLE #Garden
DROP TABLE #Flowers
Returns
Name Flower1 Flower2
-------------------- -------------------- --------------------
myGarden rose tulip
yourGarden thistle NULL
Look at using Pivot in SQL Server. Here is a good link that goes over how it works:
http://www.kodyaz.com/articles/t-sql-pivot-tables-in-sql-server-tutorial-with-examples.aspx
Ok, i think i got it working. Try this:
with temp as
(
select 'myGarden' as name, 'test1' as flower
union
select 'myGarden','test2'
union
select 'myGarden','test5'
union
select 'abeGarden','test4'
union
select 'abeGarden','test5'
union
select 'martinGarden', 'test2'
)
select* from temp
pivot
(
max(flower)
for flower in (test1,test2,test3,test4,test5)
) PivotTable
You could also make the values in the in clause dynamic. Since this is a CTE i can't in my example.
Dynamic SQL with a cursor is the only way I can think of, and it won't be pretty.
If you only want the results for one garden at a time this would give you the data:
select gardenName from tblGarden where gardenid = 1
Union ALL
select tblFLowers.flowerName from tblFlowers where gardenid = 1