I have a table:
lease_id, suite_id
Lease is a key and you can have multiple suite’s to one lease. I’m trying to create a query that shows me all of the suite’s that a lease is associated with, essentially an output like the following:
lease_id: 1 suite_list: A1, A2, B1
Unfortunately I’m unsure how to approach this (or even how to begin) as this is a new kind of problem for me... Any help would be greatly appreciated!
You can use FOR XML.
The code will be something like this:
-- Sample data tables
select *
into #leases
from (
select '1' as lease_id
union
select '2' as lease_id
) a
select *
into #leaseSuites
from (
select '1' as lease_id,
'A1' as suite_id
union
select '1' as lease_id,
'A2' as suite_id
union
select '1' as lease_id,
'B1' as suite_id
union
select '2' as lease_id,
'C2' as suite_id
union
select '2' as lease_id,
'B3' as suite_id
) a
-- Creates comma delimited with child table.
select left(suite_list, LEN(suite_list) - 1) as suite_list
from (
SELECT 'lease_id: ' + lease_id + ' ' +
'suite_list: ' + (
SELECT s.suite_id + ','
FROM #leaseSuites s
WHERE l.lease_id = s.lease_id
ORDER BY s.suite_id
FOR XML PATH('')
) AS suite_list
FROM #leases l ) a
Click here to see an article with an example.
I'll assume your table is called LeasedSuites.
We'll need a function:
create function dbo.AllSuite (#l int) returns varchar(100)
as begin
declare #v varchar(2);
declare #r varchar(100);
DECLARE sc CURSOR FOR select suite_id from LeasedSuites where lease_id = #l
OPEN sc
FETCH NEXT FROM sc INTO #v
WHILE ##FETCH_STATUS = 0 BEGIN
select #r = #r + ',' + #v;
FETCH NEXT FROM sc INTO #v
END
CLOSE sc
DEALLOCATE sc
return substring(#r, 2, len(#r) - 1);
end
And a query:
declare #l int;
create table #out (lease_id int, suite_str varchar(100) null)
insert #out (lease_id) select distinct lease_id from LeasedSuites
while (select count(*) from #out where suite_str is null) > 0 begin
select #l = min(lease_id) from #out where suite_str is null;
update #out set suite_str = dbo.AllSuite(#l) where lease_id = #l;
end
select 'Lease ID: ' + cast(lease_id as varchar(3)) + ' Suites: ' + suite_str from #out order by l;
Hope this helps. Regards JB
If this represents an answer please mark as answer.
I've ended up solving this in two ways. My first method, which was unfortunately quite slow was:
declare #period_id integer =
(
select period_id
from property.period
where getdate() between period_start and period_end
)
;with cte_data as
(
select lp.*
from property.lease_period lp
where period_id = #period_id
)
, cte_suites as
(
select d.lease_id
, (
select stuff
(
( select ', ' + a.suite_id
from
( select a.suite_id
from cte_data a
where a.lease_id = d.lease_id
) a
for xml path(''))
, 1, 2, ''
) as suite_list
) suite_list
from cte_data d
group by d.lease_id
) ,
cte_count as
(
select lease_id ,
count(suite_id) as 'suites'
from property.lease_period
where period_id = #period_id
and lease_id <> 'No Lease'
group by lease_id
)
select d.period_id ,
d.building_id ,
d.lease_id ,
s.suite_list
from cte_data d
left outer join cte_suites s
on d.lease_id = s.lease_id
inner join cte_count c
on d.lease_id = c.lease_id
where period_id = 270
and d.lease_id <> 'No Lease'
and c.suites > 1
group by
d.period_id ,
d.building_id ,
d.lease_id ,
s.suite_list
I then stripped this back and re-approached it with a new direction, resulting in the following (much, much quicker):
declare #period_id integer =
(
select period_id
from property.period
where getdate() between period_start and period_end
)
;with CteLeaseInMultSuites as
(
select period_id,
building_id,
lease_id
from property.lease_period
where period_id = #period_id
and lease_id <> 'No Lease'
group by
period_id,
building_id,
lease_id
having count(*) > 1
)
select period_id,
building_id,
lease_id,
left(x.suite_list, len(x.suite_list) - 1) as suite_list
from CteLeaseInMultSuites lm
cross apply
(
select suite_id + ', '
from CteLeaseInMultSuites lmx
inner join property.lease_period lp
on lp.period_id = lmx.period_id
and lp.building_id = lmx.building_id
and lp.lease_id = lmx.lease_id
where lmx.period_id = lm.period_id
and lmx.building_id = lm.building_id
and lmx.lease_id = lm.lease_id
for xml path('')
) x (suite_list)
Related
Folks,
I have a table with data as below.
enter image description here
Please advise sql logic to be applied to achieve this output.
One method is to use conditional aggregation:
select travelid,
max(case when seqnum = 1 then earn end) as earn,
max(case when seqnum = 1 then burn end) as burn,
max(case when seqnum = 2 then earn end) as earnA,
max(case when seqnum = 2 then burn end) as burnB
from (select t.*, row_number() over (partition by travelid order by travelid) as seqnum
from t
) t
group by travelid
SQL Server Dynamic version :
--TEST DATA
CREATE TABLE #TestTable
([TravelID] int, [SitelD] int, [Flagi] int, [FIag2] int)
;
INSERT INTO #TestTable
([TravelID], [SitelD], [Flagi], [FIag2])
VALUES
(1001, 1, 1, 0),(1001, 1, 0, 1),(1001, 1, 3, 4),
(1002, 1, 1, 0),(1002, 2, 0, 1),(1002, 2, 3, 4)
;
--STEP 1 rank data
SELECT * into #rank_table from (
select *
,ROW_NUMBER() OVER (PARTITION BY [TravelID],[SitelD] order by [SitelD]) [rank]
from (
select * from #TestTable
) T100
)T;
--STEP 2 Group by row_count
SELECT * into #group_table from (
select [TravelID],[SitelD] ,count(1) [count]
from #TestTable T
group by [TravelID],[SitelD]
)T;
--Use Exec
DECLARE #select_sql AS NVARCHAR(MAX) = ' select T.[TravelID], T.[SitelD] ',
#join_sql AS NVARCHAR(MAX) = ' from #group_table T ',
#max_count INT = (SELECT max([count]) FROM #group_table),
#temp_string NVARCHAR(5),
#temp_string_addone NVARCHAR(5)
;
DECLARE #index int = 0 ;
WHILE #index < #max_count
BEGIN
sELECT #temp_string = Convert(nvarchar(10),#index);
sELECT #temp_string_addone = Convert(nvarchar(10),#index+1);
select #select_sql = #select_sql + ' , T'+#temp_string_addone+'.[Flagi] as Flag'+Convert(nvarchar(10),2*#index+1)+' '
+ ' , T'+#temp_string_addone+'.[FIag2] as Flag'+Convert(nvarchar(10),2*#index+2)+' ';
select #join_sql = #join_sql + 'left join #rank_table T'+#temp_string_addone+' on ' + ' T.[TravelID] = T'+#temp_string_addone+'.[TravelID] and '
+ ' T.[SitelD] = T'+#temp_string_addone+'.[SitelD] and '
+ 'T'+#temp_string_addone+'.[rank] = '+#temp_string_addone+' ';
SET #index = #index + 1;
END;
EXEC (#select_sql
+ #join_sql
+' order by [TravelID],[SitelD] ; ')
;
DEMO : convert Rows to Columns in SQL with same ID, Sql Server - rextester
with CTE as(
select
*
,ROW_NUMBER() OVER (PARTITION BY [TravelID],[SitelD] order by [SitelD]) [rk]
from [Table]
)
select distinct a.[TravelID], a.[SitelD], a.[Flagi] as Flag1, a.[FIag2] as Flag1, b.[Flagi] as Flag3, b.[FIag2] as Flag4
from (select * from CTE where [rk] = 1) a
left join (select * from CTE where [rk] = 2) b on a.[TravelID] = b.[TravelID] and a.[SitelD] = + b.[SitelD]
Given few strings as
SET #Codes1 = 3,4
SET #Codes2 = 1
SET #Codes3 = --empty
Table -- TblCode
Id Code
1 A
2 B
3 C
4 D
How to convert the #Codes1, #Codes2, #Codes3 with join to the table TblCode so it returns the following output :
1. #Codes1 = CD
2. #Codes2 = A
3. #Codes3 = --empty
Note that the concatenation for the output is without the comma.
PS - This is a small example to a much larger and complex data set. Kindly ignore any wrongful design pattern here.
You can try this. I added the answer just for #Codes1, but it works with #Codes2 and #Codes3 too.
DECLARE #TblCode TABLE (Id INT, Code VARCHAR(2))
INSERT INTO #TblCode
VALUES(1, 'A'),
(2,'B'),
(3,'C'),
(4,'D')
DECLARE #Codes1 VARCHAR(10) = '3,4'
DECLARE #Codes2 VARCHAR(10) = '1'
DECLARE #Codes3 VARCHAR(10) = NULL
DECLARE #CodesOut VARCHAR(10) = ''
;WITH CTE_1 AS (
SELECT CODE= #Codes1 + ','
)
, CTE_2 AS -- It silit text to rows
(
SELECT RIGHT(CTE_1.CODE, LEN(CTE_1.CODE) - CHARINDEX(',',CTE_1.CODE)) CODE , SUBSTRING(CTE_1.CODE, 0, CHARINDEX(',',CTE_1.CODE)) ID, CHARINDEX(',',CTE_1.CODE) AS CI
FROM CTE_1
UNION ALL
SELECT RIGHT(CTE_2.CODE, LEN(CTE_2.CODE) - CHARINDEX(',',CTE_2.CODE)) CODE , SUBSTRING(CTE_2.CODE, 0, CHARINDEX(',',CTE_2.CODE)) ID, CHARINDEX(',',CTE_2.CODE) AS CI
FROM CTE_2 WHERE LEN(CTE_2.CODE) > 0
)
SELECT #CodesOut = #CodesOut + C.Code FROM CTE_2 INNER JOIN #TblCode C ON CTE_2.ID = C.Id
SELECT #CodesOut
Result:
CD
You can use a recursive CTE. Here is one method:
with c as (
select c.*, row_number() over (partition by id) as seqnum
from c
),
cte as (
select cast(#codes as varchar(max)) as str,
replace(#codes, id, code) as newstr,
1 as lev
from c
where seqnum = 1
union all
select str, replace(newstr, id, code), lev + 1
from cte join
c
on c.seqnum = cte.lev + 1
)
select top (1) newstr
from cte
order by lev desc;
If there is an error in the syntax, set up a SQL Fiddle or Rextester or something similar so it can be fixed.
I'm trying to figure our how I can replace a string using data from another table
I have a table that looks like this:
Id Translation
1 Peter
2 Sandra
3 Olga
Now I want to select all and replace the translations using a list that looks like this:
Original New
e #
r ?
lg *%
So that the select list looks like this:
Id Translation
1 P#t#?
2 Sand?a
3 O*%a
So, for each translation, I need to have a REPLACE(Translation,Original,New).
Or in other words: I need to go through every "Translation" in my first list and make another loop in my replacement table to see what to replace
Bare in mind that the first list has 25'000 rows and the second has 50'000, so I can't just type it by hand :)
EDIT
Just to clarify:
The Original and New from my look up table can be both letters and words so the table can looks like this:
Original New
one two
three fifty
sun moon
To do this in one query, you need to use a recursive CTE. Something like:
with trans as (
select t.original, t.new, row_number() over (order by t.original) as seqnum,
count(*) over () as cnt
from translations
),
t as (
select tt.id, tt.string, replace(tt.string, trans.original, trans.new) as replaced,
seqnum + 1 as seqnum, cnt
from totranslate tt join
trans
on trans.id = 1
union all
select t.id, t.string, replace(t.string, trans.original, trans.new),
seqnum + 1 as seqnum, cnt
from t join
trans
on t.seqnum = trans.id
where t.seqnum <= t.cnt
)
select t.id, t.string, t.replaced
from t
where seqnum = cnt;
You can use a UDF:
CREATE FUNCTION [dbo].[Translate]
(
-- Add the parameters for the function here
#Str nvarchar(max)
)
RETURNS nvarchar(max)
AS
BEGIN
DECLARE #Result nvarchar(max) = #Str;
SELECT #Result = replace(#Result,Original,New) from dbo.Mappings order BY Pos;
RETURN #Result;
END
Here I assumed the table containing translations is called dbo.Mappings and beside the Original and New columns you need another column Pos int which will be used to determine the order in which the translations are applied (to address the problems mentioned by #Thorsten Kettner in comments)
Also with recursive cte:
DECLARE #translations TABLE
(
Id INT ,
Translation NVARCHAR(20)
)
INSERT INTO #translations
VALUES ( 1, 'Peter' ),
( 2, 'Sandra' ),
( 3, 'Olga' )
DECLARE #replacements TABLE
(
Original VARCHAR(2) ,
New VARCHAR(2)
)
INSERT INTO #replacements
VALUES ( 'e', '#' ),
( 'r', '?' ),
( 'lg', '*%' );
WITH cte1 AS (SELECT *, ROW_NUMBER() OVER (PARTITION BY id ORDER BY (SELECT 1)) rn
FROM #translations CROSS JOIN #replacements),
cte2 AS (SELECT Id, rn, REPLACE(Translation, Original, New) AS NTranslation
FROM cte1
WHERE rn = 1
UNION ALL
SELECT c2.Id, c2.rn + 1, REPLACE(c2.NTranslation, c1.Original, c1.New)
FROM cte1 c1
JOIN cte2 c2 ON c2.Id = c1.Id AND c2.rn + 1 = c1.rn)
SELECT * FROM cte2
WHERE rn = (SELECT COUNT(*) FROM #replacements)
ORDER BY Id
EDIT:
WITH cte1 AS (SELECT t.*, p.Id AS Old, p.Code, ROW_NUMBER() OVER (PARTITION BY t.id ORDER BY (SELECT 1)) rn
FROM translations t CROSS JOIN Property p),
cte2 AS (SELECT Id, rn, REPLACE(Trans, Old, Code) AS NTranslation
FROM cte1
WHERE rn = 1
UNION ALL
SELECT c2.Id, c2.rn + 1, REPLACE(c2.NTranslation, c1.Old, c1.Code)
FROM cte1 c1
JOIN cte2 c2 ON c2.Id = c1.Id AND c2.rn + 1 = c1.rn)
SELECT * FROM cte2
WHERE rn = (SELECT COUNT(*) FROM Property)
ORDER BY Id
Here is something I worked out that will allow you to replace multiple characters with one specified string.
[Split2] is stolen from https://blogs.msdn.microsoft.com/amitjet/2009/12/11/convert-comma-separated-string-to-table-4-different-approaches/
USE <Your Database>
GO
CREATE FUNCTION [dbo].[Split2]
(
#strString varchar(4000)
)
RETURNS #Result TABLE
(
RID INT IDENTITY(0,1) Primary Key
,Value varchar(4000)
)
AS
BEGIN
WITH StrCTE(start, stop) AS
(
SELECT 1, CHARINDEX(',' , #strString )
UNION ALL
SELECT stop + 1, CHARINDEX(',' ,#strString , stop + 1)
FROM StrCTE
WHERE stop > 0
)
INSERT INTO #Result
SELECT SUBSTRING(#strString , start, CASE WHEN stop > 0 THEN stop - start ELSE 4000 END) AS stringValue
FROM StrCTE
RETURN
END
GO
USE <Your Database>
GO
CREATE FUNCTION [dbo].[MultiReplace]
(
#MyString varchar(MAX)
,#RepChars varchar(4000)
,#NewChars varchar(4000)
)
RETURNS varchar(MAX)
AS
BEGIN
DECLARE #CurRow int = 0
DECLARE #MaxRow int
SELECT #MaxRow = MAX(RID)
FROM dbo.split2 ( #RepChars )
WHILE #CurRow <= #MaxRow
BEGIN
SELECT #MyString = REPLACE(#MyString,VALUE,#NewChars)
FROM dbo.split2 ( #RepChars )
WHERE RID = #CurRow
SET #CurRow = #CurRow + 1
END
RETURN (#MyString);
END
GO
In this example I replace each character with no space
SELECT [dbo].[MultiReplace]('6th month 2016-06 (test / requested)',',1st,2nd,3rd,4th,5th,6th,0,1,2,3,4,5,6,7,8,9,(,),/,-,+, ','')
Result:
monthtestrequested
I hope this is useful for you.
I need to change an application and the first thing I need is to change a field in a database table.
In this table I now have 1 to 6 single characters, i.e. 'abcdef'
I need to change this to '[a][b][c][d][e][f]'
[edit] It is meant to stay in the same field. So before field = 'abcdef' and after field = '[a][b][c][d][e][f]'.
What would be a good way to do this?
rg.
Eric
You can split string to separate characters using following function:
create function ftStringCharacters
(
#str varchar(100)
)
returns table as
return
with v1(N) as (
select 1 union all select 1 union all select 1 union all select 1 union all select 1
union all
select 1 union all select 1 union all select 1 union all select 1 union all select 1
),
v2(N) as (select 1 from v1 a, v1 b),
v3(N) as (select top (isnull(datalength(#str), 0)) row_number() over (order by ##spid) from v2)
select N, substring(#str, N, 1) as C
from v3
GO
And then apply it as:
update t
set t.FieldName = p.FieldModified
from TableName t
cross apply (
select (select quotename(s.C)
from ftStringCharacters(t.FieldName) s
order by s.N
for xml path(''), type).value('text()[1]', 'varchar(20)')
) p(FieldModified)
SQLFiddle sample
DECLARE #text NVARCHAR(50)
SET #text = 'abcdef'
DECLARE #texttable TABLE (value NVARCHAR(1))
WHILE (len(#text) > 0)
BEGIN
INSERT INTO #texttable
SELECT substring(#text, 1, 1)
SET #text = stuff(#text, 1, 1, '')
END
select * from #texttable
Without using a function:
declare #t table(C varchar(18))
insert #t values('abc'), ('1234'), (' 1234a')
;with CTE as
(
select C, '[' + substring(c, a.n, 1) + ']' v, rn from
(select 1 n union all
select 2 union all
select 3 union all
select 4 union all
select 5 union all
select 6) a
cross apply
(select c, row_number() over (order by C) rn from #t group by c) b
where a.n <= len(C)
)
update t3
set C = t4.[value]
FROM #t t3
JOIN
(
select C,
(
select v
from CTE t1
where t1.rn = t2.rn
for xml path(''), type
).value('.', 'varchar(18)') [value]
from CTE t2
group by t2.rn, C
) t4
ON t3.C = t4.C
SELECT * FROM #t
I am using sqlserver 2008 ,I want to initialize and increment variable (#NUMTwo) both at the same time, in my second part(Problem Line).
I am creating a cte query.
Is this possible , if yes then please let me know.
following is a sample example.I hope i am clear.
CREATE table #TempTable
(
childProductID INT,parentProductID INT,productModel varchar(50),[Num2] VARCHAR(100)
)
DECLARE #NUMTwo INT = 0
WITH tableR AS
(
-- First Part
SELECT childProductID = null,parentProductID=null,productModel from Products where productid in (#a),[Num2] = convert(varchar(100), '')
UNION ALL
--Second Part
SELECT e.childProductID,e.parentProductID,prd.productModel FROM ProductIncludes AS e
,[Num2] = convert(varchar(100),'1.' + #NUMTwo+=1 ) -- Problem line
INNER JOIN Products AS PRD ON e.childProductID = PRD.productID
WHERE parentProductID in (#a)
)
INSERT INTO #TempTable(childProductID,parentProductID,productModel,[Num2])
SELECT childProductID,parentProductID,productModel,[Num2]
END
SELECT * FROM #TempTable
You need to "Initialize" a column in the acnhor part of the query, and then "oncrement" this column in the recursive parts.
Something like
DECLARE #NUMTwo INT = 0
;WITH Test AS (
SELECT [Num2] = convert(varchar(MAX), ''),
#NUMTwo [N]
UNION ALL
SELECT [Num2] = '1.' + convert(varchar(MAX),[N]+1),
[N]+1
FROM TEst
WHERE [N] < 10
)
SELECT *
FROM Test
SQL Fiddle DEMO
If the parameter #NUMTwo is just for numbering rows you can use the ROW_NUMBER() OVER(...) instead of it like so:
WITH tableR AS
(
SELECT childProductID = NULL, parentProductID = NULL,
productModel, NUMTwo = CAST('0' AS VARCHAR(10))
FROM Products
WHERE
productid in (#a),
[Num2] = convert(varchar(100), '')
UNION ALL
SELECT e.childProductID, e.parentProductID,
prd.productModel,
NUMTwo = '1.' +
CAST( ROW_NUMBER() OVER(ORDER BY (SELECT 0)) AS VARCHAR(10))
FROM ProductIncludes AS e
INNER JOIN Products AS PRD ON e.childProductID = PRD.productID
WHERE parentProductID in (#a)
)