I have a stored procedure which adds a currency record to my exchange rate table but I also need a corresponding column for the added currency.
The table structure is as follows:
CurrencyID CurrencyName Rupee Euro Dollar Pound
----------- ------------ ---------------------- ---------------------- ---------------------- ----------------------
1 Rupee 1 0.008 0.009 0.007
2 Euro 121.3 1 1.08 0.84
3 Dollar 111.4 0.91 1 0.77
4 Pound 143.6 1.18 1.28 1
and my stored procedure so far is this:
CREATE PROCEDURE addCurrency
#CurrencyName varchar(30),
#Rupee float,
#Euro float,
#Dollar float,
#Pound float
AS
BEGIN
INSERT into [dbo].[CurrencyTbl] (CurrencyName, Rupee, Euro, Dollar, Pound)
VALUES (#CurrencyName, #Rupee, #Euro, #Dollar, #Pound)
END
BEGIN
DECLARE #SQL VARCHAR(1000)
SELECT #SQL = 'ALTER TABLE [dbo].[CurrencyTbl] ADD ' + #CurrencyName + ' VARCHAR(30)'
END
But the column is not created
Not that I think any of that is a good idea, but you do not actually execute your created #SQL, you are just selecting it.
Your code vulnerable to SQL Injection (because you are directly executing sql with concatenated parameters), so be careful with that code.
Also, if you are storing numbers, why is your datatype varchar(30)? Your other datatypes are float (which should probably be a numeric/decimal instead of float).
You could use exec sp_executesql #SQL like so:
CREATE PROC addCurrency #CurrencyName varchar(30),#Rupee float,
#Euro float,#Dollar float,#Pound float
AS
BEGIN
INSERT into [dbo].[CurrencyTbl] (CurrencyName , Rupee,Euro, Dollar,Pound )
VALUES (#CurrencyName,#Rupee,#Euro,#Dollar,#Pound )
END
BEGIN
Declare #SQL nVarChar(1000)
SELECT #SQL = 'ALTER TABLE [dbo].[CurrencyTbl] ADD ' + #CurrencyName + ' float;'
exec sp_executesql #SQL;
END
dynamic sql
The curse and blessings of dynamic SQL - Erland Sommarskog
sp_executesql
For example:
/* Monies is the term used by Irkens to refer to their form of currency */
exec addCurrency 'Monies',1,1,1,1
select * from CurrencyTbl
rextester demo: http://rextester.com/CUC99912
returns:
+------------+--------------+------------+----------+----------+----------+--------+
| CurrencyID | CurrencyName | Rupee | Euro | Dollar | Pound | Monies |
+------------+--------------+------------+----------+----------+----------+--------+
| 1 | Rupee | 1,000000 | 0,008000 | 0,009000 | 0,007000 | NULL |
| 2 | Euro | 121,300000 | 1,000000 | 1,080000 | 0,840000 | NULL |
| 3 | Dollar | 111,400000 | 0,910000 | 1,000000 | 0,770000 | NULL |
| 4 | Pound | 143,600000 | 1,180000 | 1,280000 | 1,000000 | NULL |
| 5 | Monies | 1,000000 | 1,000000 | 1,000000 | 1,000000 | NULL |
+------------+--------------+------------+----------+----------+----------+--------+
It may be better to consider an alternative form for your table that does not require adding new columns and updating columns using dynamic sql.
Here is one option:
create table CurrencyTbl (FromCurrencyName varchar(30), ExchangeRate decimal(19,6), ToCurrencyName varchar(30))
insert into CurrencyTbl values
('Rupee ',1.000000,'Rupee')
,('Rupee ',0.008000,'Euro')
,('Rupee ',0.009000,'Dollar')
,('Rupee ',0.007000,'Pound')
,('Euro ',121.300000,'Rupee')
,('Euro ',1.000000,'Euro')
,('Euro ',1.090000,'Dollar')
,('Euro ',0.850000,'Pound')
,('Dollar',111.400000,'Rupee')
,('Dollar',0.910000,'Euro')
,('Dollar',1.000000,'Dollar')
,('Dollar',0.770000,'Pound')
,('Pound ',143.600000,'Rupee')
,('Pound ',1.180000,'Euro')
,('Pound ',1.280000,'Dollar')
,('Pound ',1.000000,'Pound')
And you could pivot the table dynamically like so:
declare #cols nvarchar(max);
declare #sql nvarchar(max);
select #cols = stuff((
select distinct
', ' + quotename(ToCurrencyName)
from CurrencyTbl
for xml path (''), type).value('.','nvarchar(max)')
,1,1,'')
select #sql = '
select FromCurrencyName as CurrencyName,' + #cols + '
from CurrencyTbl
pivot (max([ExchangeRate]) for [ToCurrencyName] in (' + #cols + ') ) p'
exec sp_executesql #sql;
rextester demo: http://rextester.com/EQSC62833
returns:
+--------------+----------+----------+----------+------------+
| CurrencyName | Dollar | Euro | Pound | Rupee |
+--------------+----------+----------+----------+------------+
| Dollar | 1,000000 | 0,910000 | 0,770000 | 111,400000 |
| Euro | 1,090000 | 1,000000 | 0,850000 | 121,300000 |
| Pound | 1,280000 | 1,180000 | 1,000000 | 143,600000 |
| Rupee | 0,009000 | 0,008000 | 0,007000 | 1,000000 |
+--------------+----------+----------+----------+------------+
I created Temp Tables but this ought to work with standard ones as well...
CREATE TABLE #Currencies (
Currencyid INTEGER identity(1, 1) PRIMARY KEY
,CurrencyName VARCHAR(255)
)
CREATE TABLE #ConversionRates (
CurrencyId1 INTEGER
,CurrencyId2 INTEGER
,Conversion1To2Rate DECIMAL(19, 6)
)
Then we have a stored proc to handle the work
/************************************************************************************************
Stored Procedure: dbo.usp_AddCurrency
M. Jay Wheeler -- mjwheele#yahoo.com
****************************************************************************************************/
CREATE PROCEDURE [dbo].usp_AddCurrency --
#CurrencyName VARCHAR(255) = NULL
,#CurrencyId INTEGER = NULL
,#Currency2Id INTEGER = NULL
,#Currency1ToCurrency2ExchangeRate DECIMAL(19, 6) = NULL
AS
BEGIN
DECLARE #ErrorMessage VARCHAR(MAX) = ''
DECLARE #ThrowErrorOnAddingAlreadyExists CHAR(1) = 'N' -- If N will automatically update the currency Name.
BEGIN TRY
--#############################################################################
-- Check Parameters
--#############################################################################
IF #CurrencyId IS NULL
AND #CurrencyName IS NULL
SELECT #ErrorMessage += '#CurrencyID and #CurrencyName cannot both be null.' + CHAR(13)
IF #CurrencyId IS NULL
AND NOT EXISTS (
SELECT 1
FROM #Currencies
WHERE CurrencyName = #CurrencyName
)
BEGIN
INSERT INTO #Currencies (CurrencyName)
SELECT #CurrencyName
SELECT #CurrencyId = SCOPE_IDENTITY()
END
SELECT #CurrencyId = CurrencyId
FROM #Currencies
WHERE CurrencyName = #CurrencyName
AND #CurrencyId IS NULL
SELECT #CurrencyName = CurrencyName
FROM #Currencies
WHERE Currencyid = #CurrencyId
AND #CurrencyName IS NULL
IF NOT EXISTS (
SELECT 1
FROM #Currencies
WHERE Currencyid = #CurrencyId
)
SELECT #ErrorMessage += '#CurrencyID: ' + isnull(Cast(#CurrencyId AS VARCHAR(15)), 'NULL') + ' Does Not Exist.' + CHAR(13)
IF EXISTS (
SELECT 1
FROM #Currencies c
WHERE c.Currencyid <> #CurrencyId
AND c.CurrencyName = #CurrencyName
)
SELECT #ErrorMessage += 'Currency: ' + #CurrencyName + ' already exists with an ID of ' + cast((
SELECT currencyId
FROM #Currencies
WHERE CurrencyName = #CurrencyName
) AS VARCHAR(15)) + CHAR(13)
IF EXISTS (
SELECT 1
FROM #Currencies c
WHERE c.Currencyid = #CurrencyId
AND c.CurrencyName <> #CurrencyName
)
UPDATE #Currencies
SET CurrencyName = #CurrencyName
WHERE Currencyid = #CurrencyId
IF #Currency2Id IS NOT NULL
AND #Currency1ToCurrency2ExchangeRate IS NULL
SELECT #Errormessage += 'Improper Syntax no exchange rate to assignmen.' + CHAR(13)
IF #Currency2id IS NULL
AND #Currency1ToCurrency2ExchangeRate IS NOT NULL
SELECT #Errormessage += 'Improper Syntax no "To Currency" for rate assignment.' + CHAR(13)
IF len(#ErrorMessage) > 0
RAISERROR ('%s' ,16 ,1 ,#ErrorMessage)
--#############################################################################
-- you get the idea more error checking needed to bullet proof. Now assume
-- We have all the correct Parameters set up, we've made sure anything coming in
-- needs to be added or updated by this point.
--#############################################################################
--#############################################################################
-- nothing to do if #Currency2Id is null
--#############################################################################
IF #Currency2Id IS NULL
RETURN
--#############################################################################
-- see if we need to add/Update a conversion rate.
--#############################################################################
UPDATE #ConversionRates
SET Conversion1To2Rate = #Currency1ToCurrency2ExchangeRate
WHERE #CurrencyId = CurrencyId1
AND #Currency2Id = CurrencyId2
INSERT INTO #ConversionRates (
CurrencyId1
,CurrencyId2
,Conversion1To2Rate
)
SELECT #CurrencyId
,#Currency2Id
,#Currency1ToCurrency2ExchangeRate
WHERE NOT EXISTS (
SELECT 1
FROM #ConversionRates
WHERE #CurrencyId = CurrencyId1
AND #Currency2Id = CurrencyId2
)
END TRY
BEGIN CATCH
IF ##Trancount > 0
ROLLBACK TRAN
DECLARE #ErrorBlockLineLen INTEGER = 0
DECLARE #ErrorBlockGotTheFormat BIT = 0
DECLARE #ErrorFormatIndent INTEGER = 3
DECLARE #ErrorBlockBeenThrough INTEGER = NULL -- must be set to null
DECLARE #ThisProcedureName VARCHAR(255) = ISNULL(OBJECT_NAME(##PROCID), 'Testing')
DECLARE #ErrorProc VARCHAR(4000) = CONVERT(VARCHAR(4000), ISNULL(ERROR_PROCEDURE(), #ThisProcedureName))
WHILE #ErrorBlockGotTheFormat = 0
BEGIN
IF #ErrorBlockBeenThrough IS NOT NULL
SELECT #ErrorBlockGotTheFormat = 1
SET #errormessage = Space(isnull(#ErrorFormatIndent, 0)) + #ThisProcedureName + ' Reports Error Thrown by: ' + #ErrorProc + CHAR(13)
SET #errormessage += Space(isnull(#ErrorFormatIndent, 0)) + '-------->' + ISNULL(CONVERT(VARCHAR(4000), ERROR_MESSAGE()), 'Unknown') + '<--------' + CHAR(13) -- + Space(isnull(#ErrorFormatIndent,0)) + REPLICATE('=', #ErrorBlockLineLen) + CHAR(13) -- + Space(isnull(#ErrorFormatIndent,0)) + UPPER(#ThisProcedureName + ' Variable dump:') + CHAR(13) -- + Space(isnull(#ErrorFormatIndent,0)) + REPLICATE('=', #ErrorBlockLineLen) + CHAR(13) --
+ CHAR(13) + Space(isnull(#ErrorFormatIndent, 0)) + '#Currency1ToCurrency2ExchangeRate:.....<' + ISNULL(CAST(#Currency1ToCurrency2ExchangeRate AS VARCHAR(25)), 'Null') + '>' + CHAR(13) + SPACE(ISNULL(#ErrorBlockBeenThrough, 0)) --
+ CHAR(13) + Space(isnull(#ErrorFormatIndent, 0)) + '#Currency2Id:..........................<' + ISNULL(CAST(#Currency2Id AS VARCHAR(25)), 'Null') + '>' + CHAR(13) + SPACE(ISNULL(#ErrorBlockBeenThrough, 0)) --
+ CHAR(13) + Space(isnull(#ErrorFormatIndent, 0)) + '#CurrencyId:...........................<' + ISNULL(CAST(#CurrencyId AS VARCHAR(25)), 'Null') + '>' + CHAR(13) + SPACE(ISNULL(#ErrorBlockBeenThrough, 0)) --
+ CHAR(13) + Space(isnull(#ErrorFormatIndent, 0)) + '#CurrencyName:.........................<' + ISNULL(CAST(#CurrencyName AS VARCHAR(25)), 'Null') + '>' + CHAR(13) + SPACE(ISNULL(#ErrorBlockBeenThrough, 0)) --
--SELECT #ErrorBlockLineLen = MAX(LEN(RTRIM(item)))
--FROM dbo.fnSplit(#errormessage, CHAR(13))
SELECT #ErrorBlockLineLen = 120
SELECT #ErrorBlockBeenThrough = 1
END
RAISERROR ('%s' ,16 ,1 ,#ErrorMessage)
END CATCH
END
GO
And this is the code to populate. I used the inverse for some of the rates as it was easier.
set nocount on
exec usp_AddCurrency 'Rupiee'
exec usp_AddCurrency 'Euro'
exec usp_AddCurrency 'Dollar'
exec usp_AddCurrency 'Pound'
exec usp_AddCurrency "Rupiee", Null, 1, 1.000000
exec usp_AddCurrency "Rupiee", Null, 2, 0.008000
exec usp_AddCurrency "Rupiee", Null, 3, 0.009000
exec usp_AddCurrency "Rupiee", Null, 4, 0.007000
Declare #Inverse Decimal(19,6)
Select #Inverse = cast ((1.000000/ (Select Conversion1To2Rate From #ConversionRates Where CurrencyId1 = 1 and CurrencyId2 =2 )) as decimal(19,6))
exec usp_AddCurrency 'Euro' , Null, 1, #Inverse
exec usp_AddCurrency 'Euro' , Null, 2, 1.000000
exec usp_AddCurrency 'Euro' , Null, 3, 1.090000
exec usp_AddCurrency 'Euro' , Null, 4, 0.850000
Select #Inverse = cast ((1.000000/ (Select Conversion1To2Rate From #ConversionRates Where CurrencyId1 = 1 and CurrencyId2 =3 )) as decimal(19,6))
exec usp_AddCurrency 'Dollar' , Null, 1,#Inverse
Select #Inverse = cast ((1.000000/ (Select Conversion1To2Rate From #ConversionRates Where CurrencyId1 = 2 and CurrencyId2 =3 )) as decimal(19,6))
exec usp_AddCurrency 'Dollar' , Null, 2, #Inverse
exec usp_AddCurrency 'Dollar' , Null, 3, 1.000000
exec usp_AddCurrency 'Dollar' , Null, 4, 0.770000
Select #Inverse = cast ((1.000000/ (Select Conversion1To2Rate From #ConversionRates Where CurrencyId1 = 1 and CurrencyId2 =4 )) as decimal(19,6))
exec usp_AddCurrency 'Pound' , Null, 1, #Inverse
Select #Inverse = cast ((1.000000/ (Select Conversion1To2Rate From #ConversionRates Where CurrencyId1 = 2 and CurrencyId2 =4 )) as decimal(19,6))
exec usp_AddCurrency 'Pound' , Null, 2, #Inverse
Select #Inverse = cast ((1.000000/ (Select Conversion1To2Rate From #ConversionRates Where CurrencyId1 = 3 and CurrencyId2 =4 )) as decimal(19,6))
exec usp_AddCurrency 'Pound' , Null, 3, #Inverse
exec usp_AddCurrency 'Pound' , Null, 4, 1.000000
This is the end of your question I think.
I tested the output with this code:
Declare #MySQL nVarChar(max) = 'Create Table ##output ([Currency Name] Varchar(255),' + char(13)
SELECT #MySQL += STUFF(( SELECT ',[' + CurrencyName + '] VARCHAR(255) '
FROM #Currencies
order by Currencyid asc
FOR
XML PATH('')
), 1, 1, '') + ')'
exec sp_executesql #MySQL
select * into #output from ##output
Drop Table ##Output
Declare #PivotThings varchar(max)
select #PivotThings = STUFF(( SELECT ',[' + CurrencyName + '] '
FROM #Currencies
order by Currencyid asc
FOR
XML PATH('')
), 1, 1, '')
Select #MySQL = '
SELECT *
FROM(
select c1.CurrencyName as Rows, c1.Currencyid as r, c2.CurrencyName as Cols, Conversion1To2Rate as [Value] from #ConversionRates r
join #Currencies c1 on c1.Currencyid = r.CurrencyId1
join #Currencies c2 on c2.Currencyid = r.CurrencyId2
) WorkOrders
PIVOT
(
SUM(value)
FOR [cols] IN (
##PivotThings##
)
) AS PivotTable
ORDER BY [r]
'
Select #MySQL = REPLACE(#mysql , '##PivotThings##', #pivotthings)
exec sp_executesql #mysql
Hope you find this useful. It is my first post evah!
Related
I have following data:
declare #tmptable TABLE
(
ID INT IDENTITY(1,1),
Text NVARCHAR(200)
)
insert into #tmptable values ('PREFIX_1.A + PREFIX_2.B')
insert into #tmptable values ('PREFIX_1.C + PREFIX_2.E + PREFIX_1.X')
insert into #tmptable values ('PREFIX_2.D')
insert into #tmptable values ('')
declare #suffix nvarchar(20) = '_new'
I want to replace/ append data as follows:
EXPECTED OUTPUT
PREFIX_1.A_new + PREFIX_2.B_new
PREFIX_1.C_new + PREFIX_2.E_new + PREFIX_1.X_new
PREFIX_2.D_new
What I have tried:
select REPLACE(Text,
SUBSTRING(Text, CHARINDEX('.', Text) + 1, CHARINDEX(' ',Text )),
SUBSTRING(Text, CHARINDEX('.', Text) + 1, CHARINDEX(' ',Text )) + #suffix
)
FROM #tmptable
where Text like ('%PREFIX_1%') or Text like ('%PREFIX_2%')
NOTE:
What I want:
Append suffix to the text present after the dot.
Input:
PREFIX_1.A + PREFIX_2.B - PREFIX_1.C * PREFIX_1.D
OUTPUT:
PREFIX_1.A_new + PREFIX_2.B_new - PREFIX_1.C_new * PREFIX_1.D_new
Find text just after dot(.) and append suffix to it.
Thanks
For this sample data all you have to do is replace all occurrences of ' +' with #suffix + ' +' and append also #suffix at the end:
select id, replace(Text, ' +', #suffix + ' +') + #suffix as Text
from tmptable
where Text like ('%PREFIX[_]1%') or Text like ('%PREFIX[_]2%')
See the demo.
Results:
> id | Text
> -: | :-----------------------------------------------
> 1 | PREFIX_1.A_new + PREFIX_2.B_new
> 2 | PREFIX_1.C_new + PREFIX_2.E_new + PREFIX_1.X_new
> 3 | PREFIX_2.D_new
Here are two methods.
The first one uses a recursive CTE and FOR XML to deconstruct & reconstruct the strings.
The second uses a combination of STRING_SPLIT & STRING_AGG to cut them up & glue the parts back together.
Requires Sql Server 2017+ though.
CREATE TABLE #tmpTable
(
ID INT IDENTITY(1,1) PRIMARY KEY,
[Text] NVARCHAR(200)
);
GO
✓
insert into #tmpTable ([Text]) values
('PREFIX_1.A + PREFIX_2.B')
, ('PREFIX_1.C + PREFIX_2.E + PREFIX_1.X + PREFIX_2.Y')
, ('PREFIX_2.D + PREFIX_9.E + PREFIX_1.Y')
, ('PREFIX_1.E')
, ('')
GO
5 rows affected
DECLARE #search NVARCHAR(30) = '%PREFIX[_][1-2][.]%';
DECLARE #delim VARCHAR(1) = '+';
DECLARE #suffix NVARCHAR(30) = '_new';
WITH RCTE AS
(
SELECT id
, text+#delim as str
, 1 as lvl
, 0 as pos1
, CHARINDEX(#delim,text+#delim) as pos2
FROM #tmpTable
WHERE Text LIKE (#search)
UNION ALL
SELECT id, str, lvl+1
, pos2+1
, CHARINDEX(#delim, str, pos2+1)
FROM RCTE
WHERE pos2 > pos1
)
SELECT id
, ISNULL(a.NewText, t.Text) as [Text]
FROM #tmpTable t
OUTER APPLY
(
SELECT STUFF(CAST(q.parts AS NVARCHAR(MAX)),1,3,'') as NewText
FROM
(
SELECT CONCAT(' ',#delim,' ', RTRIM(LTRIM(SUBSTRING(str, pos1, pos2-pos1))), CASE WHEN SUBSTRING(str, pos1, pos2-pos1) LIKE #search THEN #suffix END)
FROM RCTE c
WHERE c.pos2 > c.pos1
AND c.id = t.id
ORDER BY c.lvl
FOR XML PATH(''), TYPE
) q(parts)
) a
ORDER BY id;
GO
id | Text
-: | :----------------------------------------------------------------
1 | PREFIX_1.A_new + PREFIX_2.B_new
2 | PREFIX_1.C_new + PREFIX_2.E_new + PREFIX_1.X_new + PREFIX_2.Y_new
3 | PREFIX_2.D_new + PREFIX_9.E + PREFIX_1.Y_new
4 | PREFIX_1.E_new
5 |
DECLARE #search NVARCHAR(30) = '%PREFIX[_][1-2][.]%';
DECLARE #suffix NVARCHAR(30) = '_new';
SELECT id, NewText
FROM #tmpTable t
CROSS APPLY
(
SELECT
STRING_AGG(
CONCAT(RTRIM(LTRIM(s.value)),
CASE
WHEN s.value LIKE #search
THEN #suffix
END),' + ') AS NewText
FROM STRING_SPLIT(t.[Text],'+') s
) a;
GO
id | NewText
-: | :----------------------------------------------------------------
1 | PREFIX_1.A_new + PREFIX_2.B_new
2 | PREFIX_1.C_new + PREFIX_2.E_new + PREFIX_1.X_new + PREFIX_2.Y_new
3 | PREFIX_2.D_new + PREFIX_9.E + PREFIX_1.Y_new
4 | PREFIX_1.E_new
5 |
db<>fiddle here
I am writing a stored procedure with a pivot in it. The pivot field names can change depending on the data in the table.
So I have the two variables below. However this seems quite inefficient because I run two queries on the same table, this is probably due to my lack of knowledge.
declare #code nvarchar(max) = ''
select #code = #code + '[' + Code + '],' from (select Code from myTbl) as c
set #code = substring(#code , 1, len(#code ) - 1)
declare #Name nvarchar(max) = ''
select #Name = #Name + '[' + Name + '],' from (select Name from myTbl) as c
set #Name = substring(#Name , 1, len(#Name ) - 1)
Is it possible to populate both variables and only query the table once?
Yes you can, here is a simple sample
CREATE TABLE T(
Code VARCHAR(45),
Name VARCHAR(45)
);
INSERT INTO T VALUES
('Code1', 'Name1'),
('Code2', 'Name2');
DECLARE #Code VARCHAR(MAX) = '',
#Name VARCHAR(MAX) = '';
SELECT #Code = #Code + QUOTENAME(Code) + ',',
#Name = #Name + QUOTENAME(Name) + ','
FROM T;
SELECT #Code, #Name;
Returns:
+------------------+------------------+
| No column name) | (No column name) |
+------------------+------------------+
| [Code1],[Code2], | [Name1],[Name2], |
+------------------+------------------+
If you have SQL Server 2017, then no need to use substring, you can just use STRING_AGG() as
SELECT STRING_AGG(QUOTENAME(Code), ','),
STRING_AGG(QUOTENAME(Name), ',')
FROM T;
Returns:
+------------------+------------------+
| (No column name) | (No column name) |
+------------------+------------------+
| [Code1],[Code2] | [Name1],[Name2] |
+------------------+------------------+
I have stripped down the subquery (select Code from myTbl) as c since I do not think it added something in this context.
Given that. I believe it could work like this:
declare #code nvarchar(max) = ''
declare #Name nvarchar(max) = ''
select #code = #code + '[' + Code + '],', #Name = #Name + '[' + Name + '],' from myTbl
set #code = substring(#code , 1, len(#code ) - 1)
set #Name = substring(#Name , 1, len(#Name ) - 1)
Given a table that has unknown column names, how can I list all column names and the amount of rows that have a non-empty (NULL or empty string) value in that column?
Small example:
Col1 | Col2 | Col3 | dkasldk | dD? d3# !(
1 | | 2 | |
| 2 | d | ddd939 |
f | f | 84 | |
Should yield:
Column Name | Values
Col1 | 2
Col2 | 2
Col3 | 3
dkasldk | 1
dD? d3# !( | 0
I already tried around with using INFORMATION_SCHEMA.COLUMNS and creating dynamic SQL queries, but to no avail:
select N'select c.COLUMN_NAME,
(select sum(case when p.' + QUOTENAME(c.COLUMN_NAME) + ' != '''' then 1 else 0 end) from [Produkte] p) [Produkte]
from INFORMATION_SCHEMA.COLUMNS c
where c.TABLE_NAME = ''Produkte'' and c.COLUMN_NAME = ''' + c.COLUMN_NAME + '''' select_statement
from INFORMATION_SCHEMA.COLUMNS c
where c.TABLE_NAME = 'Produkte'
I cannot quite wrap my head around how to combine the two problems (for which I did find solution in their own, but not the combination) of Dynamic Column Names and Count empty values...
You can do something like this using UNPIVOT..
DECLARE #TableName NVARCHAR(MAX) = N'Produkte',
#CountColumns NVARCHAR(MAX),
#Columns NVARCHAR(MAX),
#Sql NVARCHAR(MAX)
SELECT #CountColumns = COALESCE(#CountColumns + ',', '') + 'COUNT(NULLIF(' + QUOTENAME(c.COLUMN_NAME) + ','''')) AS ' + QUOTENAME(c.COLUMN_NAME),
#Columns = COALESCE(#Columns + ',', '') + QUOTENAME(c.COLUMN_NAME)
FROM INFORMATION_SCHEMA.COLUMNS c
WHERE c.TABLE_NAME = #TableName
SET #SQL = 'SELECT [Column Name], [Values]
FROM (
SELECT '
+ #CountColumns + '
FROM ' + #TableName + '
) t
UNPIVOT (
[Values]
FOR [Column Name] IN (' + #Columns + ')
) up
'
EXEC(#SQL)
SQL Fiddle Demo
You can use a dynamic UNPIVOT.
Using these variables:
DECLARE #qry NVARCHAR(MAX)
DECLARE #cols NVARCHAR(MAX) = ''
you can select columns names in a format suitable for the UNPIVOT operation with the following dynamic sql:
SELECT #cols = STUFF((SELECT ',[' + c.COLUMN_NAME + ']'
FROM INFORMATION_SCHEMA.COLUMNS c
WHERE c.TABLE_NAME = 'Produkte'
FOR XML PATH('')), 1, 1, '')
Finally use #cols to build the query:
SET #qry = 'SELECT t.Col, COUNT(*)
FROM (SELECT Val, Col
FROM Produkte
UNPIVOT (
Val FOR Col IN (' + #cols + ')) unpvt
) AS t
WHERE t.Val <> '''' OR t.Val IS NOT NULL
GROUP BY t.Col'
... and execute it to get desired result:
EXEC(#qry)
Ultimately the SQL you are after is something like this:
SELECT ColumnName, NonEmpty
FROM ( SELECT A = 1,
[Col1] = COUNT(CASE WHEN [Col1] <> '' THEN 1 END),
[Col2] = COUNT(CASE WHEN [Col2] <> '' THEN 1 END),
[Col3] = COUNT(CASE WHEN [Col3] <> '' THEN 1 END),
[dkasldk] = COUNT(CASE WHEN [dkasldk] <> '' THEN 1 END),
[dD? d3# !(] = COUNT(CASE WHEN [dD? d3# !(] <> '' THEN 1 END)
FROM #T
) AS t
UNPIVOT
( NonEmpty
FOR ColumnName IN ([Col1],[Col2],[Col3],[dkasldk],[dD? d3# !(])
) AS upvt;
UNPIVOT Will convert your data from
Col1 Col2 Col3
-------------------------
1 3 2
To the format you require:
ColumnName NonEmpty
-------------------------
Col1 1
Col2 3
Col3 2
You can build up dynamically using something like this:
-- SAMPLE DATA
USE TempDB;
CREATE TABLE #T
(
[Col1] VARCHAR(1),
[Col2] VARCHAR(1),
[Col3] VARCHAR(2),
[dkasldk] VARCHAR(6),
[dD? d3# !(] VARCHAR(1)
);
INSERT #T ([Col1], [Col2], [Col3], [dkasldk], [dD? d3# !(])
VALUES
('1', NULL, '2', NULL, ''),
('', '2', 'd', 'ddd939', NULL),
('f', 'f', '84', NULL, '');
DECLARE #TableName SYSNAME = '#T';
-- VALID INPUT TABLE
IF OBJECT_ID(#TableName, 'U') IS NULL
AND OBJECT_ID(#TableName, 'V') IS NULL
BEGIN
PRINT 'Invalid table or View';
RETURN
END
-- BUILD DYNAMIC SQL
DECLARE #SQL NVARCHAR(MAX) =
CONCAT('SELECT ColumnName, NonEmpty FROM (SELECT A = 1, ' ,
STUFF(( SELECT CONCAT(',',
QUOTENAME(name),
' = COUNT(CASE WHEN ',
QUOTENAME(Name),
' <> '''' THEN 1 END)')
FROM sys.columns
WHERE [object_id] = OBJECT_ID(#TableName)
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, ''),
' FROM ',
#TableName,
') AS t UNPIVOT (NonEmpty FOR ColumnName IN (',
STUFF(( SELECT CONCAT(',', QUOTENAME(name))
FROM sys.columns
WHERE [object_id] = OBJECT_ID(#TableName)
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 1, ''),
')) AS upvt');
-- EXECUTE DYNAMIC SQL
EXECUTE sp_executesql #SQL;
I have a record in a single column.Like below
Address
Arulraj | Guindy | Chennai | TamilNaadu | India | 600042 | | 10000001
Adaiakalm | Chenanai | Chennai | TamilNaadu | India | 600042 | | 10000001
How to get the splitted string "TamilNaadu", "Chennai ","India " separately.
Here's a function that will split a string inline....
CREATE FUNCTION [dbo].[FN_SPLIT] ( --SELECT DBO.FN_SPLIT('TEST1 , TEST2', 2, ',')
#s varchar(512),
#i int,
#sep char(1) = ',')
RETURNS varchar(512)
AS
BEGIN
DECLARE #Ret VARCHAR(512);
WITH Pieces(pn, start, stop) AS (
SELECT 1, 1, CHARINDEX(#sep, #s)
UNION ALL
SELECT pn + 1, stop + 1, CHARINDEX(#sep, #s, stop + 1)
FROM Pieces
WHERE stop > 0
)
SELECT #Ret =
RTRIM(SUBSTRING(#s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END))
FROM Pieces
WHERE pn = #i
RETURN #Ret;
END
USAGE : SELECT DBO.FN_SPLIT('Address Arulraj | Guindy| Chennai | TamilNaadu | India | 600042 | | 10000001 ', 3, '|')
RETURNS : Chennai
Here's a table valued function that will return a table of values
CREATE FUNCTION [dbo].[FN_SPLIT_TBL](#InExp varchar(8000), #Sep varchar(10)) --SELECT * FROM DBO.[FN_SPLIT_TBL]('TEST1,TEST2', ',')
RETURNS #Res TABLE(
Pos int,
Value varchar(max))
AS
BEGIN
WITH Pieces(pn, start, stop) AS (
SELECT 1, 1, CHARINDEX(#Sep, #InExp)
UNION ALL
SELECT pn + 1, stop + 1, CHARINDEX(#sep, #InExp, stop + 1)
FROM Pieces
WHERE stop > 0
)
INSERT INTO #Res
SELECT pn, SUBSTRING(#InExp, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS s
FROM Pieces OPTION (MAXRECURSION 0);
RETURN;
END
USAGE : SELECT *
FROM DBO.FN_SPLIT_TBL('Address Arulraj | Guindy| Chennai | TamilNaadu | India | 600042 | | 10000001 ', '|')
--WHERE Pos = 3 -- Uncomment this to only return index 3
RETURNS :
Pos Value
1 Address Arulraj
2 Guindy
3 Chennai
4 TamilNaadu
5 India
6 600042
7
8 10000001
i wrote a function for this, long time ago, nothing fancy, but sufficient:
-- =============================================
-- Author: Nico Boey
-- Create date:
-- Description: Returns the n'th part of a given string, split by a given separator, with n being a zero based index
-- =============================================
ALTER FUNCTION [dbo].[GetSplitStringPart]
(
#StringToSplit nvarchar(255)
, #Separator nvarchar(10)
, #Index int
)
RETURNS nvarchar(255)
AS
BEGIN
-- Declare the return variable here
DECLARE
#Result nvarchar(255)
, #SepPos int
, #PrevSepPos int
, #currentIndex int
if #Separator is null
select #Result = #StringToSplit
else if #StringToSplit is not null and #Index > -1
begin
-- init
select #currentIndex = 0, #PrevSepPos = 0
-- read ahead
select #SepPos = charindex(#Separator , #StringToSplit)
if #SepPos = 0 select #SepPos = len(#StringToSplit) + 1
-- loop until index is reached
while #currentIndex <= #Index
begin
if #currentIndex = #Index
begin
select #Result = substring(#StringToSplit, #PrevSepPos+1, #SepPos-#PrevSepPos-1)
end
select #currentIndex = #currentIndex + 1
select #PrevSepPos = #SepPos
if #PrevSepPos = len(#StringToSplit) + 1 break;
select #SepPos = charindex(#Separator , #StringToSplit, #PrevSepPos + 1)
if #SepPos = 0 select #SepPos = len(#StringToSplit) + 1
end
end
-- Return the result of the function
RETURN #Result
END
Try this if you are using Sql Server 2012+
DECLARE #str VARCHAR(1000)='Adaiakalm | Chenanai | Chennai | TamilNaadu | India | 600042 | | 10000001',
#sql NVARCHAR(max),
#index INT=3
SET #str = '''' + Replace(#str, '|', ''',''') + ''''
SET #sql= 'select choose('+ CONVERT(VARCHAR(10), #index) + ', ' + #str + ')'
EXEC Sp_executesql #sql
Try This:
DECLARE #str VARCHAR(1000)='Adaiakalm | Chenanai | Chennai | TamilNaadu | India | 600042 | | 10000001',
#part2 INT , #part3 INT , #part4 INT , #part5 INT ,#part6 INT = 1
set #part3 = CHARINDEX('|',#str,CHARINDEX('|',#str)+1)
set #part4 = CHARINDEX('|',#str,CHARINDEX('|',#str,CHARINDEX('|',#str)+1)+1)
set #part5 = CHARINDEX('|',#str,CHARINDEX('|',#str,CHARINDEX('|',#str,CHARINDEX('|',#str)+1)+1)+1)
set #part6 = CHARINDEX('|',#str,CHARINDEX('|',#str,CHARINDEX('|',#str,CHARINDEX('|',#str,CHARINDEX('|',#str)+1)+1)+1)+1)
select SUBSTRING(#str,#part3+1,#part4 - #part3-1) As "Part-3",SUBSTRING(#str,#part4+1,#part5 - #part4-1) As "Part-4",SUBSTRING(#str,#part5+1,#part6 - #part5-1) As "Part-5"
I have written a Dynamic Pivot Table Query based on the following. Here is a SQL FIDDLE for reference.
CREATE TABLE TestTable1 ([idnumber] INT, [DataTypeId] INT)
GO
INSERT INTO TestTable1
VALUES (1, 108), (1, 108), (1, 108), (2, 108),
(2, 108), (3, 108), (1, 109),(1, 109),
(1, 110),(2, 110),(1, 111),(4, 108),(4, 108),
(4, 110),(4, 111)
GO
Here is the Dynamic SQL that I wrote
DECLARE #SQL NVARCHAR(MAX),
#Cols NVARCHAR(MAX),
#ColsP NVARCHAR(MAX)
SELECT #Cols = STUFF((select ',
ISNULL([' + CAST([DataTypeId] as varchar(10)) + '], 0) AS ''' + CAST([DataTypeId] as varchar(10)) + ''''
FROM
(
SELECT [DataTypeId] FROM [TestTable1]
GROUP BY [DataTypeId]
HAVING [DataTypeId] <> ''
) AS d
ORDER BY [DataTypeId] FOR XML PATH(''),type).value('.','varchar(max)'),1,2,'')
-- /////////////THIS IS WHAT I WANT REMOVED ////////////////////
SELECT #ColsP = STUFF((select ',
[' + CAST([DataTypeId] as varchar(10)) + ']'
FROM
(
SELECT [DataTypeId] FROM [TestTable1]
GROUP BY [DataTypeId]
HAVING [DataTypeId] <> ''
) AS d
ORDER BY [DataTypeId] FOR XML PATH(''),type).value('.','varchar(max)'),1,2,'')
-- /////////////////////////////////////////////////////////////
SET #SQL = 'SELECT idnumber,' + #Cols + '
FROM
(SELECT idnumber, COUNT([DataTypeId]) AS Total, [DataTypeId] FROM [TestTable1]
GROUP BY idnumber, [DataTypeId]
HAVING [DataTypeId] <> ''''
) p
PIVOT
(
SUM(Total) FOR [DataTypeId] IN (' + #ColsP + ')
) AS pvt
ORDER BY pvt.idnumber'
-- print #SQL
EXECUTE( #SQL)
I get the result that I want:
| IDNUMBER | 108 | 109 | 110 | 111 |
|----------|-----|-----|-----|-----|
| 1 | 3 | 2 | 1 | 1 |
| 2 | 2 | 0 | 1 | 0 |
| 3 | 1 | 0 | 0 | 0 |
| 4 | 2 | 0 | 1 | 1 |
But I am sure it can be done better. I would like to remove where I populate the variable #ColsP - SELECT #ColsP = STUFF((select...")
There should be a way where I can create this dynamic code with just one loop through TestTable1. As you can see, I loop through it twice. Once to read what columns to create for the select statement, and once for the PIVOT table.
Here is the code that is generated by the Dynamic SQL:
SELECT idnumber,
ISNULL([108], 0) AS '108',
ISNULL([109], 0) AS '109',
ISNULL([110], 0) AS '110',
ISNULL([111], 0) AS '111'
FROM
(
SELECT idnumber, COUNT([DataTypeId]) AS Total, [DataTypeId]
FROM [TestTable2]
GROUP BY idnumber, [DataTypeId]
HAVING [DataTypeId] <> ''
) p
PIVOT
(
SUM(Total) FOR [DataTypeId] IN ([108], [109], [110], [111])
) AS pvt
ORDER BY pvt.idnumber
You can shorten your code considerably. First, you can just use count to aggregate the data in the PIVOT. There is no need for the inner count to aggregate the data or the HAVING clause. Finally, you only need to create the list of columns once. You could easily improve the code to:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(DataTypeId)
from TestTable1
group by DataTypeId
order by DataTypeId
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query
= N'SELECT idnumber, ' + #cols + N'
from
(
select idnumber, DataTypeId
from TestTable1
) x
pivot
(
count(DataTypeId)
for DataTypeId in (' + #cols + N')
) p '
exec sp_executesql #query;
See SQL Fiddle with Demo. This gives the same result:
| IDNUMBER | 108 | 109 | 110 | 111 |
|----------|-----|-----|-----|-----|
| 1 | 3 | 2 | 1 | 1 |
| 2 | 2 | 0 | 1 | 0 |
| 3 | 1 | 0 | 0 | 0 |
| 4 | 2 | 0 | 1 | 1 |
Try replacing it with this.
SET NOCOUNT ON
IF OBJECT_ID('TestTable1') IS NOT NULL
DROP TABLE TestTable1
GO
CREATE TABLE TestTable1 ([idnumber] INT, [DataTypeId] INT)
GO
INSERT INTO TestTable1 VALUES
(1, 108),(1, 108),(1, 108),(2, 108),(2, 108),
(3, 108),(1, 109),(1, 109),(1, 110),(2, 110),
(1, 111),(4, 108),(4, 108),(4, 110),(4, 111)
DECLARE
#AllColumns NVARCHAR(MAX)
SELECT #AllColumns = ''
SELECT #AllColumns = #AllColumns +
'[' + CAST(DataTypeId as NVARCHAR)+'],'
FROM TestTable1
GROUP BY DataTypeId
SET #AllColumns = LEFT(#AllColumns,LEN(#AllColumns)-1)
PRINT #AllColumns