I'm trying to merge one table into another (we'll call them Stage and Prod) that controls users and their permissions. My end result should be a single Prod table that has combined each userid's permissions from Stage into Prod. The issue I'm having though is that the tables were designed by an outside vendor and contain multiple pieces of information in one comma-delimited column.
Stage might look like below:
Userid | Permissions
----------------------------------------------------------------
1 | schedule,upload,test,download,admin
2 | test,upload
3 | download
Prod:
Userid | Permissions
----------------------------------------------------------------
1 | test,admin,schedule,download,upload
2 | admin
3 | download,upload
When they're merged, the userids should have their permissions from Stage, combined with those in Prod. However, tackling this when the permissions are a comma-delimited string has me at wit's end.
In the final result below, userid 1's permissions remain unchanged because they are the same in Stage as they are in Prod, merely in a different order.
Userid 2 had his Stage permissions added to his Prod since he did not have those permissions yet.
Userid 3 had his Prod permissions unchanged since his Stage permissions are already included.
Result:
Userid | Permissions
----------------------------------------------------------------
1 | test,admin,schedule,download,upload
2 | admin,test,upload
3 | download,upload
Is there any way to do this? Hopefully this makes some sense, but if there's any more info that might help I'm happy to try to provide it. Thank you for any help at all.
Interestingly enough, this was a topic of discussion on a MSSQLTips blog by Aaron Bertrand. Borrowing his code you can create the Numbers table and string splitting/reassembling functions required to make the following work. If you are planning on doing this often and are stuck with the schema you've shown, this is the way to go.
/*Create Test Data
create table StagePermissions (UserID int, [Permissions] nvarchar(max));
create table ProdPermissions (UserID int, [Permissions] nvarchar(max));
insert StagePermissions values
(1,'schedule,upload,test,download,admin'),
(2,'test,upload'),
(3,'download')
insert ProdPermissions values
(1,'test,admin,schedule,download,upload'),
(2,'admin'),
(3,'download,upload')
*/
select sp.UserID, dbo.ReassembleString(sp.Permissions+','+pp.Permissions,',',N'OriginalOrder') MergedPermissions
from StagePermissions sp
join ProdPermissions pp on pp.UserID=sp.UserID
Taking Steve's test data, but adding:
create table BothPermissions (UserID int, [Permissions] nvarchar(max));
This code will work with a fixed number of possible permissions.
DECLARE #XPermissions TABLE (
UserID int
,XSchedule BIT
,XUpload BIT
,XTest BIT
,XDownload BIT
,XAdmin BIT
)
INSERT INTO #XPermissions
SELECT
ISNULL(sp.UserID,pp.UserID),
CHARINDEX('schedule',sp.[Permissions]) + CHARINDEX('schedule',pp.[Permissions]),
CHARINDEX('upload',sp.[Permissions]) + CHARINDEX('upload',pp.[Permissions]),
CHARINDEX('test',sp.[Permissions]) + CHARINDEX('test',pp.[Permissions]),
CHARINDEX('download',sp.[Permissions]) + CHARINDEX('download',pp.[Permissions]),
CHARINDEX('admin',sp.[Permissions]) + CHARINDEX('admin',pp.[Permissions])
FROM StagePermissions sp
FULL JOIN ProdPermissions pp
ON sp.UserID = pp.UserID
INSERT INTO BothPermissions
SELECT
UserID,
CASE XSchedule WHEN 0 THEN '' ELSE 'schedule ' END +
CASE XUpload WHEN 0 THEN '' ELSE 'upload ' END +
CASE XTest WHEN 0 THEN '' ELSE 'test ' END +
CASE XDownload WHEN 0 THEN '' ELSE 'download ' END +
CASE XAdmin WHEN 0 THEN '' ELSE 'admin' END
FROM #XPermissions
UPDATE BothPermissions
SET [Permissions] = REPLACE(RTRIM([Permissions]),' ',', ')
Now, I was further curious about Steve's answer. I think it is the most robust solution here. However, I wondered how it would perform with a large dataset. I still don't know the answer because I haven't set up the tools necessary to use it. But here's a query that includes some random number generation to populate 10,000 records of each:
SELECT GETDATE()
DECLARE #StagePerms TABLE (
UserID INT IDENTITY
,Perms NVARCHAR(MAX)
)
DECLARE #ProdPerms TABLE (
UserID INT IDENTITY
,Perms NVARCHAR(MAX)
)
DECLARE #Counter INT = 0
DECLARE #XString NVARCHAR(MAX)
WHILE #Counter < 10000
BEGIN
SET #Counter += 1
SET #XString = REPLACE(RTRIM(
CASE ROUND(RAND()-.2,0) WHEN 0 THEN '' ELSE 'test ' END +
CASE ROUND(RAND()-.2,0) WHEN 0 THEN '' ELSE 'admin ' END +
CASE ROUND(RAND()-.2,0) WHEN 0 THEN '' ELSE 'schedule ' END +
CASE ROUND(RAND()-.2,0) WHEN 0 THEN '' ELSE 'download ' END +
CASE ROUND(RAND()-.2,0) WHEN 0 THEN '' ELSE 'upload ' END)
,' ',', ')
INSERT INTO #StagePerms SELECT #XString
SET #XString = REPLACE(RTRIM(
CASE ROUND(RAND()-.2,0) WHEN 0 THEN '' ELSE 'test ' END +
CASE ROUND(RAND()-.2,0) WHEN 0 THEN '' ELSE 'admin ' END +
CASE ROUND(RAND()-.2,0) WHEN 0 THEN '' ELSE 'schedule ' END +
CASE ROUND(RAND()-.2,0) WHEN 0 THEN '' ELSE 'download ' END +
CASE ROUND(RAND()-.2,0) WHEN 0 THEN '' ELSE 'upload ' END)
,' ',', ')
INSERT INTO #ProdPerms SELECT #XString
END
SELECT GETDATE()
DECLARE #BothPerms TABLE (
UserID INT
,Perms NVARCHAR(MAX)
)
DECLARE #XPerms TABLE (
UserID int
,XSchedule BIT
,XUpload BIT
,XTest BIT
,XDownload BIT
,XAdmin BIT
)
INSERT INTO #XPerms
SELECT
ISNULL(sp.UserID,pp.UserID),
CHARINDEX('schedule',sp.Perms) + CHARINDEX('schedule',pp.Perms),
CHARINDEX('upload',sp.Perms) + CHARINDEX('upload',pp.Perms),
CHARINDEX('test',sp.Perms) + CHARINDEX('test',pp.Perms),
CHARINDEX('download',sp.Perms) + CHARINDEX('download',pp.Perms),
CHARINDEX('admin',sp.Perms) + CHARINDEX('admin',pp.Perms)
FROM #StagePerms sp
FULL JOIN #ProdPerms pp
ON sp.UserID = pp.UserID
INSERT INTO #BothPerms
SELECT
UserID,
CASE XTest WHEN 0 THEN '' ELSE 'test ' END +
CASE XAdmin WHEN 0 THEN '' ELSE 'admin ' END +
CASE XSchedule WHEN 0 THEN '' ELSE 'schedule ' END +
CASE XDownload WHEN 0 THEN '' ELSE 'download ' END +
CASE XUpload WHEN 0 THEN '' ELSE 'upload ' END
FROM #XPerms
UPDATE #BothPerms
SET Perms = REPLACE(RTRIM(Perms),' ',', ')
SELECT * FROM #BothPerms
SELECT GETDATE()
The random number generation took less than a second; the rest took about 31 seconds. Steve, I'd be interested to see a comparison. Doesn't matter, obviously, if the data doesn't allow for my solution. And I'm sure there's a sweet spot somewhere.
Please make use of the below query. Its working fine in SQL Server 2012.
DECLARE #Stage TABLE (Userid int, Permission Varchar (8000))
DECLARE #Prod TABLE (Userid int, Permission Varchar (8000))
DECLARE #temp TABLE (Userid int, Permission Varchar (8000))
INSERT #Stage
(Userid,Permission)
VALUES
(1,'schedule,upload,test,download,admin'),
(2,'test,upload'),
(3,'download')
INSERT #Prod
(Userid,Permission)
VALUES
(1,'test,admin,schedule,download,upload'),
(2,'admin'),
(3,'download,upload')
-- Execution Part
INSERT INTO #temp
(Userid,Permission)
(
SELECT A.Userid AS Userid,Split.a.value('.', 'VARCHAR(100)') AS Permission FROM
(SELECT Userid,CAST ('<M>' + REPLACE(Permission, ',', '</M><M>') + '</M>' AS XML) AS Permission FROM #Stage A) AS A
CROSS APPLY Permission.nodes ('/M') AS Split(a)
UNION
SELECT A.Userid AS Userid,Split.a.value('.', 'VARCHAR(100)') AS Permission FROM
(SELECT Userid,CAST ('<M>' + REPLACE(Permission, ',', '</M><M>') + '</M>' AS XML) AS Permission FROM #Prod A) AS A
CROSS APPLY Permission.nodes ('/M') AS Split(a)
)
SELECT Userid, Permission =
STUFF((SELECT ', ' + Permission
FROM #temp b
WHERE b.Userid = a.Userid
FOR XML PATH('')), 1, 2, '')
FROM #temp a
GROUP BY Userid
OUTPUT
Userid Permission
1 admin, download, schedule, test, upload
2 admin, test, upload
3 download, upload
You can also use direct support of string splitting introduced in SQL Serv 2016 (in case you started using this engine version already of course :) )
STRING_SPLIT returns single column table...
Related
Given the below table and data:
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp
CREATE TABLE #Temp
(
ID INT,
Code INT,
PDescription VARCHAR(2000)
)
INSERT INTO #Temp
(ID,
Code,
PDescription)
VALUES (1,0001,'c and d, together'),
(2,0002,'equals or Exceeds $27.00'),
(3,0003,'Fruit Evaporating Or preserving'),
(4,0004,'Domestics And domestic Maintenance'),
(5,0005,'Bakeries and cracker')
SELECT *
FROM #Temp
DROP TABLE #Temp
Output:
ID Code PDescription
1 1 c and d, together
2 2 equals or Exceeds $27.00
3 3 Fruit Evaporating Or preserving
4 4 Domestics And domestic Maintenance
5 5 Bakeries and cracker
I need a way to achieve the below update to the description field:
ID Code PDescription
1 1 C and D, Together
2 2 Equals or Exceeds $27.00
3 3 Fruit Evaporating or Preserving
4 4 Domestics and Domestic Maintenance
5 5 Bakeries and Cracker
If you fancied going the SQL CLR route the function could look something like
using System.Data.SqlTypes;
using System.Text.RegularExpressions;
public partial class UserDefinedFunctions
{
//One or more "word characters" or apostrophes
private static readonly Regex _regex = new Regex("[\\w']+");
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlString ProperCase(SqlString subjectString)
{
string resultString = null;
if (!subjectString.IsNull)
{
resultString = _regex.Replace(subjectString.ToString().ToLowerInvariant(),
(Match match) =>
{
var word = match.Value;
switch (word)
{
case "or":
case "of":
case "and":
return word;
default:
return char.ToUpper(word[0]) + word.Substring(1);
}
});
}
return new SqlString(resultString);
}
}
Doubtless there may be Globalization issues in the above but it should do the job for English text.
You could also investigate TextInfo.ToTitleCase but that still leaves you needing to handle your exceptions.
The following function is not the most elegant of solutions but should do what you want.
ALTER FUNCTION [dbo].[ToProperCase](#textValue AS NVARCHAR(2000))
RETURNS NVARCHAR(2000)
AS
BEGIN
DECLARE #reset BIT;
DECLARE #properCase NVARCHAR(2000);
DECLARE #index INT;
DECLARE #character NCHAR(1);
SELECT #reset = 1, #index=1, #properCase = '';
WHILE (#index <= len(#textValue))
BEGIN
SELECT #character= substring(#textValue,#index,1),
#properCase = #properCase + CASE WHEN #reset=1 THEN UPPER(#character) ELSE LOWER(#character) END,
#reset = CASE WHEN #character LIKE N'[a-zA-Z\'']' THEN 0 ELSE 1 END,
#index = #index +1
END
SET #properCase = N' ' + #properCase + N' ';
SET #properCase = REPLACE(#properCase, N' And ', N' and ');
SET #properCase = REPLACE(#properCase, N' Or ', N' or ');
SET #properCase = REPLACE(#properCase, N' Of ', N' of ');
RETURN RTRIM(LTRIM(#properCase))
END
Example use:
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp
CREATE TABLE #Temp
(
ID INT,
Code INT,
PDescription VARCHAR(2000)
)
INSERT INTO #Temp
(ID,
Code,
PDescription)
VALUES (1,0001, N'c and d, together and'),
(2,0002, N'equals or Exceeds $27.00'),
(3,0003, N'Fruit Evaporating Or preserving'),
(4,0004, N'Domestics And domestic Maintenance'),
(5,0005, N'Bakeries and cracker')
SELECT ID, Code, dbo.ToProperCase(PDescription) AS [Desc]
FROM #Temp
DROP TABLE #Temp
If you want to convert your text to proper case before inserting into table, then simply call function as follow:
INSERT INTO #Temp
(ID,
Code,
PDescription)
VALUES (1,0001, dbo.ToProperCase( N'c and d, together and')),
(2,0002, dbo.ToProperCase( N'equals or Exceeds $27.00')),
(3,0003, dbo.ToProperCase( N'Fruit Evaporating Or preserving')),
(4,0004, dbo.ToProperCase( N'Domestics And domestic Maintenance')),
(5,0005, dbo.ToProperCase( N'Bakeries and cracker'))
This is a dramatically modified version of my Proper UDF. The good news is you may be able to process the entire data-set in ONE SHOT rather than linear.
Take note of #OverR (override)
Declare #Table table (ID int,Code int,PDescription varchar(150))
Insert into #Table values
(1,1,'c and d, together'),
(2,2,'equals or Exceeds $27.00'),
(3,3,'Fruit Evaporating Or preserving'),
(4,4,'Domestics And domestic Maintenance'),
(5,5,'Bakeries and cracker')
-- Generate Base Mapping Table - Can be an Actual Table
Declare #Pattn table (Key_Value varchar(25));Insert into #Pattn values (' '),('-'),('_'),(','),('.'),('&'),('#'),(' Mc'),(' O''') -- ,(' Mac')
Declare #Alpha table (Key_Value varchar(25));Insert Into #Alpha values ('A'),('B'),('C'),('D'),('E'),('F'),('G'),('H'),('I'),('J'),('K'),('L'),('M'),('N'),('O'),('P'),('Q'),('R'),('S'),('T'),('U'),('V'),('W'),('X'),('Y'),('X')
Declare #OverR table (Key_Value varchar(25));Insert Into #OverR values (' and '),(' or '),(' of ')
Declare #Map Table (MapSeq int,MapFrom varchar(25),MapTo varchar(25))
Insert Into #Map
Select MapSeq=1,MapFrom=A.Key_Value+B.Key_Value,MapTo=A.Key_Value+B.Key_Value From #Pattn A Join #Alpha B on 1=1
Union All
Select MapSeq=99,MapFrom=A.Key_Value,MapTo=A.Key_Value From #OverR A
-- Convert Base Data Into XML
Declare #XML xml
Set #XML = (Select KeyID=ID,String=+' '+lower(PDescription)+' ' from #Table For XML RAW)
-- Convert XML to varchar(max) for Global Search & Replace
Declare #String varchar(max)
Select #String = cast(#XML as varchar(max))
Select #String = Replace(#String,MapFrom,MapTo) From #Map Order by MapSeq
-- Convert Back to XML
Select #XML = cast(#String as XML)
-- Generate Final Results
Select KeyID = t.col.value('#KeyID', 'int')
,NewString = ltrim(rtrim(t.col.value('#String', 'varchar(150)')))
From #XML.nodes('/row') AS t (col)
Order By 1
Returns
KeyID NewString
1 C and D, Together
2 Equals or Exceeds $27.00
3 Fruit Evaporating or Preserving
4 Domestics and Domestic Maintenance
5 Bakeries and Cracker
You don't even need functions and temporary objects. Take a look at this query:
WITH Processor AS
(
SELECT ID, Code, 1 step,
CONVERT(nvarchar(MAX),'') done,
LEFT(PDescription, CHARINDEX(' ', PDescription, 0)-1) process,
SUBSTRING(PDescription, CHARINDEX(' ', PDescription, 0)+1, LEN(PDescription)) waiting
FROM #temp
UNION ALL
SELECT ID, Code, step+1,
done+' '+CASE WHEN process IN ('and', 'or', 'of') THEN LOWER(process) ELSE UPPER(LEFT(process, 1))+LOWER(SUBSTRING(process, 2, LEN(process))) END,
CASE WHEN CHARINDEX(' ', waiting, 0)>0 THEN LEFT(waiting, CHARINDEX(' ', waiting, 0)-1) ELSE waiting END,
CASE WHEN CHARINDEX(' ', waiting, 0)>0 THEN SUBSTRING(waiting, CHARINDEX(' ', waiting, 0)+1, LEN(waiting)) ELSE NULL END FROM Processor
WHERE process IS NOT NULL
)
SELECT ID, Code, done PSDescription FROM
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY step DESC) RowNum FROM Processor
) Ordered
WHERE RowNum=1
ORDER BY ID
It produces desired result as well. You can SELECT * FROM Processor to see all steps executed.
In some situations, I got a weird behavior on SQL Server 2012 (latest update) when trying to generate a string using self concatenation
#Str += ...
or
#Str = #Str + ...
It's truncating the previous content of the variable within the query, which is the expected behavior when concatenating a NULL value except I'm not...
Here is a simplified version of the code reduced to the minimum to reproduce the bug on my instance. It's hard to reproduce as just copying the function result to a temp table (which is impossible in my case) fixes it, so I'm suspecting something around query planning or optimization.
DECLARE #CTESQL VARCHAR(MAX)= '';
SELECT
--TOP 4096--Workaround for SQL SERVER bug dropping previous text in some cases (4096 = max statement in a select clause)
#CTESQL+= CASE WHEN 1 = ROW_NUMBER() OVER (ORDER BY PvtColumnName) THEN '1'
ELSE CASE WHEN LAG(PvtColumnName) OVER (ORDER BY PvtColumnName) <> ISNULL(PvtColumnName,
ColumnName)
THEN '2'
ELSE '3'
END
END + CASE WHEN PvtColumnName IS NULL THEN '4'
ELSE (CASE WHEN 1 = ROW_NUMBER() OVER (ORDER BY t.PvtColumnName DESC)
THEN '5'
ELSE '6'
END)
END
FROM
dbo.ImportDefinition('stgPopulation') t
ORDER BY
PvtColumnName
, ColumnId
PRINT (#CTESQL);
The table function 'ImportDefinition' is returning the following data:
PvtColumnName ColumnId ColumnName
------------------- ----------- --------------------
NULL 3 Country
NULL 2 GMPSubRegion
NULL 4 ISO_Ctry
NULL 9 Source
AgeGroupCode 6 Total
AreaTypeCode 6 Total
AgeGroupCode 7 Under5
AreaTypeCode 7 Under5
AgeGroupCode 8 Urban
AreaTypeCode 8 Urban
NULL 1 RegionFullName
NULL 5 Year
the expected result is :
343414343434363636363625
the actual result from SQL Server is :
25
A simple work around is to use 'TOP n' which fix it but I don't know why and it's pretty dirty.
I had some hope that forcing MAXDOP 1 would help but no luck there.
It is the 2nd time I'm running against this issue so despite having multiple workaround I'd really would like to understand what is happening or if there is a bug somewhere.
Thank you for your expertise.
EDIT
Here is a script that allow to reproduce the same behavior:
IF OBJECT_ID('dbo.MyTable', 'U') IS NOT NULL
DROP TABLE dbo.MyTable;
CREATE TABLE dbo.MyTable
(
F1 VARCHAR(255) NOT NULL
, F2 NVARCHAR(4000) NULL
)
ON [PRIMARY];
GO
INSERT INTO dbo.MyTable
(F1, F2)
VALUES
('foo', 'a')
, ('faa', 'b')
, ('fuu', 'a');
DECLARE #CTESQL VARCHAR(MAX)= '';
SELECT
#CTESQL+= CASE WHEN 1 = ROW_NUMBER() OVER (ORDER BY F2)
THEN '1'
ELSE CASE WHEN LAG(F2) OVER (ORDER BY F2) <> ISNULL(F2,
F1)
THEN '2'
ELSE '3'
END
END + CASE WHEN F2 IS NULL THEN '4'
ELSE (CASE WHEN 1 = ROW_NUMBER() OVER (ORDER BY F2 DESC)
THEN '5'
ELSE '6'
END)
END
FROM
MyTable
ORDER BY
F2;
PRINT (#CTESQL);
Sorry just re-read and noticed that copying the results of the function to a table prevents the issue from reproducing.
Unless you want to create scripts for the base table(s) and table-valued function that would allow others to reproduce the issue, the best anyone can do is guess.
My first guess is that your function isn't returning the results you think, but if it is, then there is something about the interplay between a TVF and the undocumented technique you are using to build a string from query results.
I highlight undocumented to remind you that is exactly what this technique that you are using is, and it is impossible to say that there is a "bug" in undocumented behavior. SQL was never intended to work the way you are using it, and it's just chance that it happens to work that way most of the time, but there is no guarantee that it will work that way all the time, or at all in future versions. Even fixing it with TOP n is undocumented and may not work in a future version of SQL Server.
The better solution would be to start using STUFF() to do your string concatenation. There are already numerous examples of how to do this on this site and elsewhere on the internet.
As to the question "Why isn't this working?", I suspect the best answer you're going to get is: "It's undocumented behavior. Who knows?"
EDIT in response to comments:
The undocumented technique I am referring to is the building of a string variable using +=. See this article, and scroll down to the section called "Unreliable Approaches". The approach you are using is the second one listed, "Scalar UDF with variable concatenation in SELECT", although you are not using it in a UDF. Still, the technique of SELECT #var = #var + SomeData... is the part that undocumented and therefore unreliable.
The "solution using STUFF()" I was referring to is the same as the one proposed by SqlZim in his answer. The same solution employs both STUFF() and FOR XML. As shorthand, I referred to it as using STUFF(), as I knew a search on that keyword would lead to that solution.
The only way I was able to reproduce your issue was by removing the '+' in #ctesql+=.
You might give the stuff() version below a try and see if you have the same issue.
use TempDb
go
set nocount on;
--if exists (select * from tempdb.sys.objects where name like '#ImportDefinition%') begin; drop table #ImportDefinition; end;
--/*
if not exists (select * from tempdb.sys.objects where name like '#ImportDefinition%')
begin;
create table #ImportDefinition (PvtColumnName nvarchar(16) ,ColumnId smallint ,ColumnName nvarchar(16) )
insert into #ImportDefinition values (null ,'3' ,'Country') ,(null ,'2' ,'GMPSubRegion') ,(null ,'4' ,'ISO_Ctry') ,(null ,'9' ,'Source') ,('AgeGroupCode' ,'6' ,'Total') ,('AreaTypeCode' ,'6' ,'Total') ,('AgeGroupCode' ,'7' ,'Under5') ,('AreaTypeCode' ,'7' ,'Under5') ,('AgeGroupCode' ,'8' ,'Urban') ,('AreaTypeCode' ,'8' ,'Urban') ,(null ,'1' ,'RegionFullName') ,(null ,'5' ,'Year');
end;
-- select * from #ImportDefinition
--*/
declare #ctesql varchar(max)= '';
--/*
select
#ctesql+=(case when 1 = row_number() over (order by pvtcolumnname) then '1'
when lag(pvtcolumnname) over (order by pvtcolumnname) <> isnull(pvtcolumnname, columnname) then '2'
else '3'
end)
+ (case when pvtcolumnname is null then '4'
when 1 = row_number() over (order by t.pvtcolumnname desc) then '5'
else '6'
end)
from #importdefinition t
order by pvtcolumnname, columnid;
print (#ctesql);
declare #ForXmlPath varchar(max)
select #ForXmlPath = stuff((select
(case when 1 = row_number() over (order by pvtcolumnname) then '1'
when lag(pvtcolumnname) over (order by pvtcolumnname) <> isnull(pvtcolumnname, columnname) then '2'
else '3'
end)
+ (case when pvtcolumnname is null then '4'
when 1 = row_number() over (order by t.pvtcolumnname desc) then '5'
else '6'
end)
from #importdefinition t
order by pvtcolumnname, columnid
for xml path (''), type).value('.','varchar(max)'),1,0,'');
print #ForXmlPath;
--*/
print char(10);
print ##version;
declare #options int = ##options;
print 'disable_def_cnst_chk' + case when 1 & #options = 1 then ' on' else ' off' end;
print 'implicit_transactions' + case when 2 & #options = 2 then ' on' else ' off' end;
print 'cursor_close_on_commit' + case when 4 & #options = 4 then ' on' else ' off' end;
print 'ansi_warnings' + case when 8 & #options = 8 then ' on' else ' off' end;
print 'ansi_padding' + case when 16 & #options = 16 then ' on' else ' off' end;
print 'ansi_nulls' + case when 32 & #options = 32 then ' on' else ' off' end;
print 'arithabort' + case when 64 & #options = 64 then ' on' else ' off' end;
print 'arithignore' + case when 128 & #options = 128 then ' on' else ' off' end;
print 'quoted_identifier' + case when 256 & #options = 256 then ' on' else ' off' end;
print 'nocount' + case when 512 & #options = 512 then ' on' else ' off' end;
print 'ansi_null_dflt_on' + case when 1024 & #options = 1024 then ' on' else ' off' end;
print 'ansi_null_dflt_off' + case when 2048 & #options = 2048 then ' on' else ' off' end;
print 'concat_null_yields_null'+ case when 4096 & #options = 4096 then ' on' else ' off' end;
print 'numeric_roundabort' + case when 8192 & #options = 8192 then ' on' else ' off' end;
print 'xact_abort' + case when 16384 & #options = 16384 then ' on' else ' off' end;
go
results in this:
343414343434363636363625
343414343434363636363625
Microsoft SQL Server 2012 - 11.0.5058.0 (X64)
May 14 2014 18:34:29
Copyright (c) Microsoft Corporation
Developer Edition (64-bit) on Windows NT 6.1 <X64> (Build 7601: Service Pack 1) (Hypervisor)
disable_def_cnst_chk off
implicit_transactions off
cursor_close_on_commit off
ansi_warnings on
ansi_padding on
ansi_nulls on
arithabort on
arithignore off
quoted_identifier on
nocount on
ansi_null_dflt_on on
ansi_null_dflt_off off
concat_null_yields_null on
numeric_roundabort off
xact_abort off
I want this procedure change the table name when I execute it.
The table name that I want to change is Recargas_#mes
There is some way to do that?
#MES DATETIME
AS
BEGIN
SELECT CUENTA, SUM(COSTO_REC) COSTO_REC
INTO E09040_DEV.BI_PRO_COSTO_RECARGAS
FROM (
SELECT a.*,(CASE
WHEN COD_AJUSTE IN ('ELEC_TEXT','TFREPPVV_C') THEN (A.VALOR)*(R.COSTO) ELSE 0 END)
FROM Recargas_#MES AS A, BI_PRO_LISTA_COSTOS_RECARGAS AS R
WHERE R.ANO_MES = #MES
) D
GROUP BY CUENTA
END
Sample code:
-- Declare variables
DECLARE #MES DATETIME;
DECLARE #TSQL NVARCHAR(MAX);
-- Set the variable to valid statement
SET #TSQL = N'
SELECT CUENTA, SUM(COSTO_REC) AS COSTO_REC
INTO E09040_DEV.BI_PRO_COSTO_RECARGAS
FROM (
SELECT A.*,
(CASE
WHEN COD_AJUSTE IN (''ELEC_TEXT'',''TFREPPVV_C'') THEN
(A.VALOR)*(R.COSTO)
ELSE 0
END)
FROM
Recargas_' + REPLACE(CONVERT(CHAR(10), #MES, 101), '/', '') + ' AS A,
BI_PRO_LISTA_COSTOS_RECARGAS AS R
WHERE R.ANO_MES = ' + CONVERT(CHAR(10), #MES, 101) + '
) D
GROUP BY CUENTA'
-- Execute the statement
EXECUTE (#SQL)
Some things to note:
1 - I assume the table name has some type of extension that is a date? I used MM/DD/YYYY and removed the slashes as a format for the suffix.
2 - The WHERE clause will only work if you are not using the time part of the variable.
For instance, 03/15/2016 00:00:00 would be date without time entry. If not, you will have to use >= and < to grab all hours for a particular day.
3 - You are creating a table on the fly with this code. On the second execution, you will get a error unless you drop the table.
4 - You are not using the ON clause when joining table A to table R. To be ANSI compliant, move the WHERE clause to a ON clause.
5 - The actual calculation created by the CASE statement is not give a column name.
Issues 3 to 5 have to be solved on your end since I do not have the detailed business requirements.
Have Fun.
It should work using dynamic SQL to allow putting a dynamic table name:
DECLARE #SQL NVARCHAR(MAX) = N'
SELECT CUENTA, SUM(COSTO_REC) COSTO_REC
INTO E09040_DEV.BI_PRO_COSTO_RECARGAS
FROM (
SELECT a.*,(CASE
WHEN COD_AJUSTE IN (''ELEC_TEXT'',''TFREPPVV_C'') THEN (A.VALOR)*(R.COSTO) ELSE 0 END)
FROM Recargas_' + #MES + ' AS A, BI_PRO_LISTA_COSTOS_RECARGAS AS R
WHERE R.ANO_MES = ' + CAST(#MES AS VARCHAR(32)) + '
) D
GROUP BY CUENTA'
EXECUTE (#SQL)
I want to check if all given word fragments exist in any order in a given text.
The fragments are supplied by a web application user in a single string separated by spaces like 'abc xyz kj'. They exist in 'mn kj qabc pc xyzw' but do not exist in 'mn kj qabc pc xyw'.
I wrote the following function which works but it looks quite convoluted so I must be doing it wrong. Any ideas on different approaches or how to make it perform?
BTW the database is read only for me so I can't full-text index it and the owners will not do it.
create function dbo.tem_fragmentos(
#texto varchar(max),
#fragmentos varchar(max)
)
returns bit as
begin
declare
#inicio integer = 1,
#fim integer,
#fragmento varchar(max);
set #fragmentos = ltrim(rtrim(#fragmentos));
while charindex(' ', #fragmentos) > 0
set #fragmentos = replace(#fragmentos, ' ', ' ');
while #inicio <= len(#fragmentos) begin
set #fim = charindex(' ', #fragmentos, #inicio + 1);
if #fim = 0 set #fim = len(#fragmentos) + 1;
set #fragmento = substring(#fragmentos, #inicio, #fim - #inicio);
if charindex(#fragmento, #texto) = 0 return 0;
set #inicio = #fim + 1;
end -- while
return 1;
end;
select dbo.tem_fragmentos('clodoaldo pinto neto', ' clo cl nto pinto');
This is how I would do it. Not sure it's any less convoluted...
Create Function dbo.tem_fragmentos
(
#texto varchar(max),
#fragmentos varchar(max)
)
Returns Bit As
Begin
Declare #table Table (fragmentos Varchar(Max))
Set #fragmentos = Ltrim(Rtrim(#fragmentos))
While #fragmentos <> ''
Begin
Insert #table (fragmentos)
Select Left(#fragmentos,Charindex(' ',#fragmentos+' ')-1)
Set #fragmentos = Ltrim(Rtrim(Right(#fragmentos,Len(#fragmentos)-(Charindex(' ',#fragmentos+' ')-1))));
end
If Exists (Select 1
From #table t
Where #texto Not Like '%' + fragmentos + '%')
Begin
Return 0;
End
Return 1;
End;
Select dbo.tem_fragmentos('clodoaldo pinto neto', ' clo cl nto pinto');
I'm assuming your text exists in a db table, else you wouldn't have the db server doing the work. So, why not have your app break the string on spaces and build dynamic SQL like:
select *
from MyTable
where charindex('abc', MyColumn) > 0
and charindex('xyz', MyColumn) > 0
and charindex('kj', MyColumn) > 0
Update:
If you don't want to use dynamic SQL, I would split the input into words in my application, and then pass the list of words in to the query using a table valued parameter (TVP). Then it is a simple left join to determine whether they all match or not.
Sounds like a wildcarded LIKE search should work for you:
declare #texto varchar(max) = 'mn kj q abc pc xyzw',
#fragmentos varchar(max) = 'abc xyz kj'
/*
yes = 'mn kj qabc pc xyzw'
no = 'mn kj qabc pc xyw'
*/
--use your own number table
declare #number table (n int identity(1,1) primary key clustered, x char(1) null);
insert into #number(x)
select top 1000 null from master..spt_values
select [IsMatch] = min(case when #texto like '%'+substring(#fragmentos, n, charindex(' ', #fragmentos + ' ', n) - n)+'%' then 1 else 0 end)
from #number
where n <= datalength(#fragmentos)+1 and
substring(' ' + #fragmentos, N, 1) = ' ';
I have to variables that contain comma-separated strings:
#v1 = 'hello, world, one, two'
#v2 = 'jump, down, yes, one'
I need a function that will return TRUE if there is at least one match. So in the above example, it would return TRUE since the value 'one' is in both strings.
Is this possible in SQL?
Use a split function (many examples here - CLR is going to be your best option in most cases back before SQL Server 2016 - now you should use STRING_SPLIT()).
Once you have a split function, the rest is quite easy. The model would be something like this:
DECLARE #v1 VARCHAR(MAX) = 'hello, world, one, two',
#v2 VARCHAR(MAX) = 'jump, down, yes, one';
SELECT CASE WHEN EXISTS
(
SELECT 1
FROM dbo.Split(#v1) AS a
INNER JOIN dbo.Split(#v2) AS b
ON a.Item = b.Item
)
THEN 1 ELSE 0 END;
You can even reduce this to only call the function once:
SELECT CASE WHEN EXISTS
(
SELECT 1 FROM dbo.Split(#v1)
WHERE ', ' + LTRIM(#v2) + ','
LIKE '%, ' + LTRIM(Item) + ',%'
) THEN 1 ELSE 0 END;
On 2016+:
SELECT CASE WHEN EXISTS
(
SELECT 1 FROM STRING_SPLIT(#v1, ',')
WHERE ', ' + LTRIM(#v2) + ','
LIKE '%, ' + LTRIM([Value]) + ',%'
) THEN 1 ELSE 0 END;
You can use CTEs to split your string into xml nodes, then insert the words into table variables. Joining the table variables will reveal any matches
DECLARE #v1 VARCHAR(200) = 'hello, world, one, two'
DECLARE #v2 VARCHAR(200) = 'jump, down, yes, one'
DECLARE #v1Words TABLE (word VARCHAR(100))
DECLARE #v2Words TABLE (word VARCHAR(100))
;WITH cteSplitV1 AS(
SELECT CAST('<word>' + REPLACE(#v1,', ','</word><word>') + '</word>' AS XML) AS words)
INSERT INTO #v1Words(word)
SELECT word.x.value('.','VARCHAR(100)') AS [word]
FROM cteSplitV1
CROSS APPLY words.nodes('/word') AS word(x)
;WITH cteSplitV2 AS(
SELECT CAST('<word>' + REPLACE(#v2,', ','</word><word>') + '</word>' AS XML) AS words)
INSERT INTO #v2Words(word)
SELECT word.x.value('.','VARCHAR(100)') AS [word]
FROM cteSplitV2
CROSS APPLY words.nodes('/word') AS word(x)
SELECT *
FROM #v1Words v1
JOIN #v2Words v2
ON v1.word = v2.word