I have a dataset with one of the columns as Transaction_Date in which date varies from the year 2005 to 2018.
I need to assign a distinct year to a variable, later on, I will be using the same variable in SQL Pivot.
-- variable declaration
DECLARE #PCOL VARCHAR(20);
-- assigning values to the variable
SELECT #PCOL += (QUOTENAME (X.TD) + ',')
FROM
(
SELECT DISTINCT YEAR(TRANSACTION_DATE) AS TD
FROM TRANSACTION_INFO
) AS X;
-- Check the result
PRINT #PCOL
It is not resulting me the output as expected. Please suggest.
You need FOR XML PATH() clause :
SELECT #PCOL = STUFF( (SELECT DISTINCT ', '+ QUOTENAME(CAST(YEAR(TRANSACTION_DATE) AS VARCHAR(255)))
FROM TRANSACTION_INFO
FOR XML PATH('')
), 1, 1, ''
)
You're getting NULL because you didn't pre-set your variable to an empty string before you used the += operator.
Since NULL + 'some value' = NULL, your variable never gets changed from NULL to something else.
It looks like this is what you're attempting to do...
IF OBJECT_ID('tempdb..#TRANSACTION_INFO', 'U') IS NOT NULL
BEGIN DROP TABLE #TRANSACTION_INFO; END;
CREATE TABLE #TRANSACTION_INFO (
TRANSACTION_DATE DATE
);
INSERT #TRANSACTION_INFO (TRANSACTION_DATE) VALUES
('20130101'),('20130101'),('20140101'),
('20140102'),('20150102'),('20150102'),
('20160103'),('20160103'),('20170104'),
('20170104'),('20180105'),('20180105');
--================================================
DECLARE #POL VARCHAR(200) = '';
SELECT
#POL = CONCAT(#POL, ',', x.TD)
FROM (
SELECT DISTINCT
TD = YEAR(ti.TRANSACTION_DATE)
FROM
#TRANSACTION_INFO ti
) x;
SET #POL = STUFF(#POL, 1, 1, '');
PRINT(#POL);
Result:
2013,2014,2015,2016,2017,2018
Related
I am working on a query that I need to modify so that a string is passed to in(). The view table is being used by some other view table and ultimately by a stored procedure. The string values must be in ' '.
select region, county, name
from vw_main
where state - 'MD'
and building_id in ('101', '102') -- pass the string into in()
The values for the building_id will be entered at the stored procedure level upon its execution.
Please check below scripts which will give you answer.
Way 1: Split CSV value using XML and directly use select query in where condition
DECLARE #StrBuildingIDs VARCHAR(1000)
SET #StrBuildingIDs = '101,102'
SELECT
vm.region,
vm.county,
vm.name
FROM vw_main vm
WHERE vm.state = 'MD'
AND vm.building_id IN
(
SELECT
l.value('.','VARCHAR(20)') AS Building_Id
FROM
(
SELECT CAST('<a>' + REPLACE(#StrBuildingIDs,',','</a><a>') + '</a>') AS BuildIDXML
) x
CROSS APPLY x.BuildIDXML.nodes('a') Split(l)
)
Way 2: Split CSV value using XML, Create Variable Table and use that in where condition
DECLARE #StrBuildingIDs VARCHAR(1000)
SET #StrBuildingIDs = '101,102'
DECLARE #TblBuildingID TABLE(BuildingId INT)
INSERT INTO #TblBuildingID(BuildingId)
SELECT
l.value('.','VARCHAR(20)') AS Building_Id
FROM
(
SELECT CAST('<a>' + REPLACE(#StrBuildingIDs,',','</a><a>') + '</a>') AS BuildIDXML
) x
CROSS APPLY x.BuildIDXML.nodes('a') Split(l)
SELECT
vm.region,
vm.county,
vm.name
FROM vw_main AS vm
WHERE vm.state = 'MD'
AND vm.building_id IN
(
SELECT
BuildingId
FROM #TblBuildingID
)
Way 3: Split CSV value using XML, Create Variable Table and use that in INNER JOIN
Assuming the input string is not end-user input, you can do this. That is, derived or pulled from another table or other controlled source.
DECLARE #in nvarchar(some length) = N'''a'',''b'',''c'''
declare #stmt nvarchar(4000) = N'
select region, county, name
from vw_main
where state = ''MD''
and building_id in ({instr})'
set #stmt = replace(#stmt, N'{instr}', #instr)
exec sp_executesql #stmt=#stmt;
If the input is from an end-user, this is safer:
declare # table (a int, b char)
insert into #(a, b) values (1,'A'), (2, 'B')
declare #str varchar(50) = 'A,B'
select t.* from # t
join (select * from string_split(#str, ',')) s(b)
on t.b = s.b
You may like it better anyway, since there's no dynamic sql involved. However you must be running SQL Server 2016 or higher.
I have a string like this:
Apple
I want to include a separator after each character so the end result will turn out like this:
A,p,p,l,e
In C#, we have one liner method to achieve the above with Regex.Replace('Apple', ".{1}", "$0,");
I can only think of looping each character with charindex to append the separator but seems a little complicated. Is there any elegant way and simpler way to achieve this?
Thanks HABO for the suggestions. I'm able to generate the result that I want using the code but takes a little bit of time to really understand how the code work.
After some searching, I manage to found one useful article to insert empty spaces between each character and it's easier for me to understand.
I modify the code a little to define and include desire separator instead of fixing it to space as the separator:
DECLARE #pos INT = 2 -- location where we want first space
DECLARE #result VARCHAR(100) = 'Apple'
DECLARE #separator nvarchar(5) = ','
WHILE #pos < LEN(#result)+1
BEGIN
SET #result = STUFF(#result, #pos, 0, #separator);
SET #pos = #pos+2;
END
select #result; -- Output: A,p,p,l,e
Reference
In following SQL scripts, I get each character using SUBSTRING() function using with a number table (basically I used spt_values view here for simplicity) and then I concatenate them via two different methods, you can choose one
If you are using SQL Server 2017, we have a new SQL string aggregation function
First script uses string_agg function
declare #str nvarchar(max) = 'Apple'
SELECT
string_agg( substring(#str,number,1) , ',') Within Group (Order By number)
FROM master..spt_values n
WHERE
Type = 'P' and
Number between 1 and len(#str)
If you are working with a previous version, you can use string concatenation using FOR XML Path and SQL Stuff function as follows
declare #str nvarchar(max) = 'Apple'
; with cte as (
SELECT
number,
substring(#str,number,1) as L
FROM master..spt_values n
WHERE
Type = 'P' and
Number between 1 and len(#str)
)
SELECT
STUFF(
(
SELECT
',' + L
FROM cte
order by number
FOR XML PATH('')
), 1, 1, ''
)
Both solution yields the same result, I hope it helps
If you have SQL Server 2017 and a copy of ngrams8k it's ultra simple:
declare #word varchar(100) = 'apple';
select newString = string_agg(token, ',') within group (order by position)
from dbo.ngrams8k(#word,1);
For pre-2017 systems it's almost as simple:
declare #word varchar(100) = 'apple';
select newstring =
( select token + case len(#word)+1-position when 1 then '' else ',' end
from dbo.ngrams8k(#word,1)
order by position
for xml path(''))
One ugly way to do it is to split the string into characters, ideally using a numbers table, and reassemble it with the desired separator.
A less efficient implementation uses recursion in a CTE to split the characters and insert the separator between pairs of characters as it goes:
declare #Sample as VarChar(20) = 'Apple';
declare #Separator as Char = ',';
with Characters as (
select 1 as Position, Substring( #Sample, 1, 1 ) as Character
union all
select Position + 1,
case when Position & 1 = 1 then #Separator else Substring( #Sample, Position / 2 + 1, 1 ) end
from Characters
where Position < 2 * Len( #Sample ) - 1 )
select Stuff( ( select Character + '' from Characters order by Position for XML Path( '' ) ), 1, 0, '' ) as Result;
You can replace the select Stuff... line with select * from Characters; to see what's going on.
Try this
declare #var varchar(50) ='Apple'
;WITH CTE
AS
(
SELECT
SeqNo = 1,
MyStr = #var,
OpStr = CAST('' AS VARCHAR(50))
UNION ALL
SELECT
SeqNo = SeqNo+1,
MyStr = MyStR,
OpStr = CAST(ISNULL(OpStr,'')+SUBSTRING(MyStR,SeqNo,1)+',' AS VARCHAR(50))
FROM CTE
WHERE SeqNo <= LEN(#var)
)
SELECT
OpStr = LEFT(OpStr,LEN(OpStr)-1)
FROM CTE
WHERE SeqNo = LEN(#Var)+1
As Azure DW is not supporting FOR XML and SELECT variable assignment, is there any other way to convert multiple rows into single row except using CURSOR?
I didn't found any direct method however the below code is working for me.
DECLARE #intColumnCount INT,
#intProcessCount INT,
#varColList VARCHAR(max)
SET #varColList = ''
IF Object_id('tempdb.dbo.#tempColumnNames') IS NOT NULL
BEGIN
DROP TABLE #tempcolumnnames;
END
CREATE TABLE #tempcolumnnames
(
intid INT,
varcolumnnames VARCHAR(256)
)
INSERT INTO #tempcolumnnames
SELECT Row_number()
OVER (
ORDER BY NAME),
NAME
FROM sys.tables
SET #intProcessCount = 1
SET #intColumnCount = (SELECT Count(*)
FROM #tempcolumnnames)
WHILE ( #intProcessCount <= #intColumnCount )
BEGIN
SET #varColList = #varColList + ', '
+ (SELECT varcolumnnames
FROM #tempcolumnnames
WHERE intid = #intProcessCount)
SET #intProcessCount +=1
END
SELECT Stuff(#varColList, 1, 2, '')
Hope this helps someone.
I have a procedure like this;
SET #Oldname = (SELECT name
FROM [Marketing].[dbo].[_Users]
WHERE ID = #MID);
Everything is okay but some members got xxx, xxx_A1,xxx_A2,xxx_A3, while selecting these members I want to select without _A1 , _A2 , A_30
How can I select it?
Try reversing string, find first index of _ and apply substring:
DECLARE #s NVARCHAR(MAX) = 'John_Smith_234'
DECLARE #t TABLE ( Name NVARCHAR(50) )
INSERT INTO #t
VALUES ( 'John_Smith_234' ),
( 'Michael_Jordan' ),
( 'Honore_De_Balzak_234' )
SELECT SUBSTRING(Name, 1, LEN(Name) - CHARINDEX('_', REVERSE(Name))) AS Name
FROM #t
Output:
Name
John_Smith
Michael
Honore_De_Balzak
try this
select left(name, len(name)-charindex('_', reverse(name))) name
from whatever
Assure that you add a '%' sign to your #MID Variable, looking like 'xxx%'
Than SELECT with LIKE instead of = :
SET #Oldname = (SELECT name From [Marketing].[dbo].[_Users] Where ID LIKE #MID);
I think you may also add the '%' sign in qour query (haven't tried):
SET #Oldname = (SELECT name From [Marketing].[dbo].[_Users] Where ID LIKE #MID+'%');
I have following query which takes 2 parameters.
YearNumber
MonthNumber
In my pivot query, I am trying to select columns based on #Year_Rtl variable. I need to select data for the year passed, last year and last last year. Since the data being displayed on UI is table format divided by #Year_Rtl, I decided to write a pivot query for that as below.
In the query, it works fine if I hard code [#Year_Rtl], [#Year_Rtl - 1], [#Year_Rtl - 2] to [2012], [2011], [2010]. But since the year passed can be anything, I want columns to be named dynamically.
DECLARE #Month_Rtl int
DECLARE #Year_Rtl int
SET #Year_Rtl = 2012
SET #Month_Rtl = 1
SELECT
'Data 1', [#Year_Rtl], [#Year_Rtl - 1], [#Year_Rtl - 2]
FROM
(SELECT [Yr_No], Qty
FROM dbo.Table1 t
WHERE (t.Col1 = 10) AND
(t.Col2 = '673') AND
((t.Mth_No = #Month_Rtl AND t.Yr_No = #Year_Rtl) OR
(t.Mth_No = 12 AND t.Yr_No IN (#Year_Rtl - 1, #Year_Rtl - 2)))
) p PIVOT (SUM(Qty)
FOR [Yr_No] IN ([#Year_Rtl], [#Year_Rtl-1], [#Year_Rtl-2])
) AS pvt
Above query throws following errors:
Error converting data type nvarchar to smallint.
The incorrect value "#Year_Rtl" is supplied in the PIVOT operator.
Invalid column name '#Year_Rtl - 1'.
Invalid column name '#Year_Rtl - 2'.
Since you can use dynamic SQL, I'd go with a macro-replacement approach. You're identifying areas of the query that must be dynamically replaced with placeholders (e.g. $$Year_Rtl) and then calculating their replacement values below. I find that it keeps the SQL statement easy to follow.
DECLARE #SQL NVarChar(2000);
SELECT #SQL = N'
SELECT
''Data 1'', [$$Year_Rtl], [$$Year_RtlM1], [$$Year_RtlM2]
FROM
(SELECT [Yr_No], Qty
FROM dbo.Table1 t
WHERE (t.Col1 = 10) AND
(t.Col2 = ''673'') AND
((t.Mth_No = $$Month_Rtl AND t.Yr_No = $$Year_Rtl) OR
(t.Mth_No = 12 AND t.Yr_No IN ($$Year_RtlM1, $$Year_RtlM2)))
) p PIVOT (SUM(Qty)
FOR [Yr_No] IN ([$$Year_Rtl], [$$Year_RtlM1], [$$Year_RtlM2])
) AS pvt';
SELECT #SQL = REPLACE(#SQL, '$$Year_RtlM2', #Year_Rtl - 2);
SELECT #SQL = REPLACE(#SQL, '$$Year_RtlM1', #Year_Rtl - 1);
SELECT #SQL = REPLACE(#SQL, '$$Year_Rtl', #Year_Rtl);
SELECT #SQL = REPLACE(#SQL, '$$Month_Rtl', #Month_Rtl);
PRINT #SQL;
-- Uncomment the next line to allow the built query to execute...
--EXECUTE sp_ExecuteSQL #SQL;
Since consuming code will also have to be flaky under this scheme (e.g. selecting columns based on "position" rather than name) - why not normalize the columns by performing a DATEDIFF(year,Yr_No,#Year_Rtl), and work from there? Those columns will always be 0, -1 and -2...
You need to look into Dynamic SQL Pivoting.
I recommend reading Itzik Ben-Gan's T-SQL Fundamentals where he goes over how to do this.
Alternatively try this article if you don't want to buy the book.
Maybe this will help:
First getting the columns with a tally function like this:
DECLARE #Month_Rtl int,
#Year_Rtl int,
#Year_Rtl_Start INT,
#cols VARCHAR(MAX),
#values VARCHAR(MAX)
SET #Year_Rtl = 2012
SET #Month_Rtl = 1
SET #Year_Rtl_Start=2009
;WITH Years ( n ) AS (
SELECT #Year_Rtl_Start UNION ALL
SELECT 1 + n FROM Years WHERE n < #Year_Rtl )
SELECT
#cols = COALESCE(#cols + ','+QUOTENAME(n),
QUOTENAME(n)),
#values = COALESCE(#values + ','+CAST(n AS VARCHAR(100)),
CAST(n AS VARCHAR(100)))
FROM
Years
ORDER BY n DESC
The variable #cols contains the columns that is in the pivot and the variable #values contains the years for the IN. The #Year_Rtl is the end year and the #Year_Rtl_Start is the start for you range.
Then declaring and executing the dynamic pivot like this:
DECLARE #query NVARCHAR(4000)=
N'SELECT
''Data 1'', '+#cols+'
FROM
(
SELECT
[Yr_No], Qty
FROM
dbo.Table1 t
WHERE
t.Col1 = 10
AND t.Col2 = ''673''
AND
(
(
t.Mth_No = '+CAST(#Month_Rtl AS VARCHAR(10))+'
AND t.Yr_No = '+CAST(#Year_Rtl AS VARCHAR(10))+'
)
OR
(
t.Mth_No = 12
AND t.Yr_No IN ('+#values+'))
)
) p
PIVOT
(
SUM(Qty)
FOR [Yr_No] IN ('+#cols+')
) AS pvt'
EXECUTE(#query)