Break up Text string to use in Join in SQL Server 2008 - sql

Good Day,
I am busy creating a tax document for discount entries out of our debtors transaction table. One of the requirements is that the discount amount must indicate to which transaction it relates. This information is available but I do not know how to use it because it is in a single delimited text field. The table looks as follows.
╔═════════╦════════════╦══════╦════════╦═══════════╦══════════════════════════════════════════╗
║ AutoIdx ║ TxDate ║ Id ║ Amount ║ Reference ║ cAllocs ║
╠═════════╬════════════╬══════╬════════╬═══════════╬══════════════════════════════════════════╣
║ 1 ║ 30-07-2014 ║ OInv ║ 100 ║ IN543 ║ I=4;A=99;D=20140730|I=3;A=1;D=20140730 ║
║ 2 ║ 30-07-2014 ║ OInv ║ 200 ║ IN544 ║ I=4;A=198;D=20140730|I=3;A=2;D=20140730 ║
║ 3 ║ 30-07-2014 ║ DS ║ 3 ║ DISC ║ I=1;A=1;D=20140730|I=2;A=2;D=20140730 ║
║ 4 ║ 30-07-2014 ║ Pmnt ║ 297 ║ PMNT ║ I=1;A=99;D=20140730|I=2;A=198;D=20140730 ║
╚═════════╩════════════╩══════╩════════╩═══════════╩══════════════════════════════════════════╝
In the column cAllocs you have the information that links the lines with each other. It has three indicators
I - the line in this table that it links to
A - the amount of the link transaction
D - the date of the transaction
It can have only one set of these or many sets delimited with a pipe character.
I want to end up with a table that has only the entries with Id "DS" but giving me the reference column value of the lines it links to in a single column (they can be delimited by comma)
So from the above table I would like to get this result.
╔════════════╦════╦════════╦═══════════╦═════════════╗
║ TxDate ║ Id ║ Amount ║ Reference ║ MatchedTo ║
╠════════════╬════╬════════╬═══════════╬═════════════╣
║ 30-07-2014 ║ DS ║ 3 ║ DISC ║ IN543,IN544 ║
╚════════════╩════╩════════╩═══════════╩═════════════╝
Thanks for any replies

You can create a function that handles string manipulation and retrieves the data at the same time. Try this one below:
CREATE FUNCTION [dbo].[GetReferenceFromCAllocs](#cAllocs VARCHAR(MAX))RETURNS VARCHAR(MAX)AS
BEGIN
DECLARE #val AS VARCHAR(MAX)
DECLARE #ch AS CHAR(1)
DECLARE #result AS VARCHAR(MAX)
DECLARE #returnValue AS VARCHAR(MAX)
SET #val = #cAllocs
SET #result = ''
SET #returnValue = ''
DECLARE #idx INT
SET #idx = 1
WHILE #idx < LEN(#val)
BEGIN
SET #ch = SUBSTRING(#val,#idx,1)
IF(#ch = 'I')
BEGIN
DECLARE #idx2 INT
DECLARE #temp VARCHAR(5)
SET #temp = #ch
SET #idx2 = #idx + 1
SET #ch = SUBSTRING(#val,#idx2,1)
WHILE #ch <> ';'
BEGIN
SET #temp = #temp + #ch
SET #idx2 = #idx2 + 1
SET #ch = SUBSTRING(#val,#idx2,1)
END
SET #idx = #idx2
SET #result = #result + #temp + ';'
END
ELSE
SET #idx = #idx + 1
END
SET #idx = 1
WHILE #idx < LEN(#result)
BEGIN
SET #ch = SUBSTRING(#val,#idx,1)
IF(#ch = '=')
BEGIN
SET #temp = ''
SET #idx2 = #idx + 1
SET #ch = SUBSTRING(#result,#idx2,1)
WHILE #ch <> ';'
BEGIN
SET #temp = #temp + #ch
SET #idx2 = #idx2 + 1
SET #ch = SUBSTRING(#result,#idx2,1)
END
SET #idx = #idx2
DECLARE #tempValue AS VARCHAR(10)
SET #tempValue = ''
SELECT #tempValue = Reference FROM YOUR_TABLE
WHERE AutoIdx = #temp
SET #returnValue = #returnValue + #tempValue + ','
END
ELSE
SET #idx = #idx + 1
END
RETURN SUBSTRING(#returnValue,1, LEN(#returnValue)-1)
END
--EDIT: The function above will get all data with I as it's prefix:
E.g:
Passed Value: I=4;A=99;D=20140730|I=3;A=1;D=20140730
Result Value: I=4;I=3;
After that, it queries the corresponding Reference with AutoIdx 4 and 3; then returns it in a format as specified in your question.
Then, run this sql:
SELECT
yt.TxDate,
yt.Id,
yt.Amount,
yt.Reference,
dbo.GetReferenceFromCAllocs(yt.cAllocs) AS MatchedTo
FROM YOUR_TABLE yt
WHERE yt.Id = 'DS'
Hope this would work. Just adjust the SQL codes to optimize it. I'm in a hurry so I used safest values (such as VARCHAR(MAX)).

Related

how to substring an empty phrase in sql?

I have a link and I want to substring it into many pieces to get numbers
example:-
&OR=-12&MR=24560&MM=20&Mx=110&EOW=1&OW=50&OV=12&CV=-1
I want the -12 , 24560 , 20 , ... etc.
and I did it using this code
DECLARE #strprog AS NVARCHAR(MAX)--,#strLM AS NVARCHAR(20),#strLOW AS NVARCHAR(20),#strLOR AS NVARCHAR(20),#strLCR AS NVARCHAR(20),
DECLARE #OR AS SMALLINT, #MR AS SMALLINT, #MM AS SMALLINT, #Mx AS SMALLINT, #EOW AS SMALLINT, #OW AS SMALLINT, #OV AS SMALLINT, #CV AS SMALLINT
SET #strprog='WorkFlow/WFmain.aspx?sservice=WFAppraisalManage&showrep=1&applyRole=0&DisableApproval=1&grbCompQuest=1&EnablecompletionChk=1
&MaxCompletionNo=110&ProficiencyValidation=0&objresulttxt=0
&OR=-12&MR=24560&MM=20&Mx=110&EOW=1&OW=50&OV=12&CV=-1'
SET #OR= SUBSTRING(#strprog,CHARINDEX('&OR=',#strprog)+4,CASE WHEN CHARINDEX('&',#strprog,CHARINDEX('&OR=',#strprog)+4 ) <=0 THEN LEN(#strprog)+1 ELSE CHARINDEX('&',#strprog,CHARINDEX('&OR=',#strprog)+4 ) END -CHARINDEX('&OR=',#strprog)-4 )
SELECT #OR
SET #MR= SUBSTRING(#strprog,CHARINDEX('&MR=',#strprog)+4,CASE WHEN CHARINDEX('&',#strprog,CHARINDEX('&MR=',#strprog)+4 ) <=0 THEN LEN(#strprog)+1 ELSE CHARINDEX('&',#strprog,CHARINDEX('&MR=',#strprog)+4 ) END -CHARINDEX('&MR=',#strprog)-4 )
SELECT #MR
SET #MM= SUBSTRING(#strprog,CHARINDEX('&MM=',#strprog)+4,CASE WHEN CHARINDEX('&',#strprog,CHARINDEX('&MM=',#strprog)+4 ) <=0 THEN LEN(#strprog)+1 ELSE CHARINDEX('&',#strprog,CHARINDEX('&MM=',#strprog)+4 ) END -CHARINDEX('&MM=',#strprog)-4 )
SELECT #MM
SET #Mx= SUBSTRING(#strprog, CHARINDEX('&Mx=',#strprog)+4 ,CASE WHEN CHARINDEX('&',#strprog,CHARINDEX('&Mx=',#strprog)+4 ) <=0 THEN LEN(#strprog)+1 ELSE CHARINDEX('&',#strprog,CHARINDEX('&Mx=',#strprog)+4 ) END -CHARINDEX('&Mx=',#strprog)-4 )
SELECT #Mx
SET #EOW= SUBSTRING(#strprog,CHARINDEX('&EOW=',#strprog)+5,CASE WHEN CHARINDEX('&',#strprog,CHARINDEX('&EOW=',#strprog)+5 ) <=0 THEN LEN(#strprog)+1 ELSE CHARINDEX('&',#strprog,CHARINDEX('&EOW=',#strprog)+5 ) END -CHARINDEX('&EOW=',#strprog)-5 )
SELECT #EOW
SET #OW= SUBSTRING(#strprog,CHARINDEX('&OW=',#strprog)+4,CASE WHEN CHARINDEX('&',#strprog,CHARINDEX('&OW=',#strprog)+4 ) <=0 THEN LEN(#strprog)+1 ELSE CHARINDEX('&',#strprog,CHARINDEX('&OW=',#strprog)+4 ) END -CHARINDEX('&OW=',#strprog)-4 )
SELECT #OW
SET #OV= SUBSTRING(#strprog,CHARINDEX('&OV=',#strprog)+4,CASE WHEN CHARINDEX('&',#strprog,CHARINDEX('&OV=',#strprog)+4 ) <=0 THEN LEN(#strprog)+1 ELSE CHARINDEX('&',#strprog,CHARINDEX('&OV=',#strprog)+4 ) END -CHARINDEX('&OV=',#strprog)-4 )
SELECT #OV
SET #CV= SUBSTRING(#strprog,CHARINDEX('&CV=',#strprog)+4,CASE WHEN CHARINDEX('&',#strprog,CHARINDEX('&CV=',#strprog)+4 ) <=0 THEN LEN(#strprog)+1 ELSE CHARINDEX('&',#strprog,CHARINDEX('&CV=',#strprog)+4 ) END -CHARINDEX('&CV=',#strprog)-4 )
SELECT #CV
The problem is the link could be cut off at any place
example:-
&OR=-12&MR=24560&MM=20
how can I stop cutting when it reach an end ?
any help ?
You can use XML type in MSSQL server to separate the string into lines and get a table with attribute and value columns. Then you can select any attribute you need from this rowset:
DECLARE #strprog AS NVARCHAR(MAX);
SET #strprog='WorkFlow/WFmain.aspx?sservice=WFAppraisalManage&showrep=1&
applyRole=0&DisableApproval=1&grbCompQuest=1&EnablecompletionChk=1
&MaxCompletionNo=110&ProficiencyValidation=0&objresulttxt=0
&OR=-12&MR=24560&MM=20&Mx=110&EOW=1&OW=50&OV=12&CV=-1';
DECLARE #xml as XML;
SET #xml = cast(('<X>'+replace(#strprog,'&' ,'</X><X>')+'</X>') as xml);
WITH T2 as
(
SELECT N.value('.', 'varchar(1000)') as val FROM #xml.nodes('X') as T(N)
)
SELECT LEFT(val,CHARINDEX('=',val)-1) as attribute,
SUBSTRING(val,CHARINDEX('=',val)+1,1000) as value
FROM T2;
You get following row set:
╔═══════════════════════════════╦═══════════════════╗
║ attribute ║ value ║
╠═══════════════════════════════╬═══════════════════╣
║ WorkFlow/WFmain.aspx?sservice ║ WFAppraisalManage ║
║ showrep ║ 1 ║
║ applyRole ║ 0 ║
║ DisableApproval ║ 1 ║
║ grbCompQuest ║ 1 ║
║ EnablecompletionChk ║ 1 ║
║ MaxCompletionNo ║ 110 ║
║ ProficiencyValidation ║ 0 ║
║ objresulttxt ║ 0 ║
║ OR ║ -12 ║
║ MR ║ 24560 ║
║ MM ║ 20 ║
║ Mx ║ 110 ║
║ EOW ║ 1 ║
║ OW ║ 50 ║
║ OV ║ 12 ║
║ CV ║ -1 ║
╚═══════════════════════════════╩═══════════════════╝
All your substrings are preempted by an equal sign and finished by an ampersand.
Why don't you create a loop to get the string, get value from first equal to the ampersand, drop the part of the string processed from the string and continue doing the same with the rest of the string??
You can check the CHARINDEX of each token before setting each variable to see if that token even exists at all in the string.
DECLARE #strprog AS NVARCHAR(MAX)--,#strLM AS NVARCHAR(20),#strLOW AS NVARCHAR(20),#strLOR AS NVARCHAR(20),#strLCR AS NVARCHAR(20),
DECLARE #OR AS SMALLINT, #MR AS SMALLINT, #MM AS SMALLINT, #Mx AS SMALLINT, #EOW AS SMALLINT, #OW AS SMALLINT, #OV AS SMALLINT, #CV AS SMALLINT
--SELECT #MR =0, #MM =0, #Mx=0 , #EOW=0, #OW =0, #OV=0
--&OR=-12&MR=24560&MM=20&Mx=110&EOW=1&OW=50&OV=12&CV=-1
SET #strprog='WorkFlow/WFmain.aspx?sservice=WFAppraisalManage&showrep=1&applyRole=0&DisableApproval=1&grbCompQuest=1&EnablecompletionChk=1
&MaxCompletionNo=110&ProficiencyValidation=0&objresulttxt=0
&OR=-12&MR=24560&Mx=110&EOW=1&OW=50&OV=12'
IF CHARINDEX('&OR=',#strprog) <> 0
SET #OR= SUBSTRING(#strprog,CHARINDEX('&OR=',#strprog)+4,CASE WHEN CHARINDEX('&',#strprog,CHARINDEX('&OR=',#strprog)+4 ) <=0 THEN LEN(#strprog)+1 ELSE CHARINDEX('&',#strprog,CHARINDEX('&OR=',#strprog)+4 ) END -CHARINDEX('&OR=',#strprog)-4 )
SELECT #OR
IF CHARINDEX('&MR=',#strprog) <> 0
SET #MR= SUBSTRING(#strprog,CHARINDEX('&MR=',#strprog)+4,CASE WHEN CHARINDEX('&',#strprog,CHARINDEX('&MR=',#strprog)+4 ) <=0 THEN LEN(#strprog)+1 ELSE CHARINDEX('&',#strprog,CHARINDEX('&MR=',#strprog)+4 ) END -CHARINDEX('&MR=',#strprog)-4 )
SELECT #MR
IF CHARINDEX('&MM=',#strprog) <> 0
SET #MM= SUBSTRING(#strprog,CHARINDEX('&MM=',#strprog)+4,CASE WHEN CHARINDEX('&',#strprog,CHARINDEX('&MM=',#strprog)+4 ) <=0 THEN LEN(#strprog)+1 ELSE CHARINDEX('&',#strprog,CHARINDEX('&MM=',#strprog)+4 ) END -CHARINDEX('&MM=',#strprog)-4 )
SELECT #MM
IF CHARINDEX('&Mx=',#strprog) <> 0
SET #Mx= SUBSTRING(#strprog, CHARINDEX('&Mx=',#strprog)+4 ,CASE WHEN CHARINDEX('&',#strprog,CHARINDEX('&Mx=',#strprog)+4 ) <=0 THEN LEN(#strprog)+1 ELSE CHARINDEX('&',#strprog,CHARINDEX('&Mx=',#strprog)+4 ) END -CHARINDEX('&Mx=',#strprog)-4 )
SELECT #Mx
IF CHARINDEX('&EOW=',#strprog) <> 0
SET #EOW= SUBSTRING(#strprog,CHARINDEX('&EOW=',#strprog)+5,CASE WHEN CHARINDEX('&',#strprog,CHARINDEX('&EOW=',#strprog)+5 ) <=0 THEN LEN(#strprog)+1 ELSE CHARINDEX('&',#strprog,CHARINDEX('&EOW=',#strprog)+5 ) END -CHARINDEX('&EOW=',#strprog)-5 )
SELECT #EOW
IF CHARINDEX('&OW=',#strprog) <> 0
SET #OW= SUBSTRING(#strprog,CHARINDEX('&OW=',#strprog)+4,CASE WHEN CHARINDEX('&',#strprog,CHARINDEX('&OW=',#strprog)+4 ) <=0 THEN LEN(#strprog)+1 ELSE CHARINDEX('&',#strprog,CHARINDEX('&OW=',#strprog)+4 ) END -CHARINDEX('&OW=',#strprog)-4 )
SELECT #OW
IF CHARINDEX('&OV=',#strprog) <> 0
SET #OV= SUBSTRING(#strprog,CHARINDEX('&OV=',#strprog)+4,CASE WHEN CHARINDEX('&',#strprog,CHARINDEX('&OV=',#strprog)+4 ) <=0 THEN LEN(#strprog)+1 ELSE CHARINDEX('&',#strprog,CHARINDEX('&OV=',#strprog)+4 ) END -CHARINDEX('&OV=',#strprog)-4 )
SELECT #OV
IF CHARINDEX('&CV=',#strprog) <> 0
Set #CV= SUBSTRING(#strprog,CHARINDEX('&CV=',#strprog)+4,CASE WHEN CHARINDEX('&',#strprog,CHARINDEX('&CV=',#strprog)+4 ) <=0 THEN LEN(#strprog)+1 ELSE CHARINDEX('&',#strprog,CHARINDEX('&CV=',#strprog)+4 ) END -CHARINDEX('&CV=',#strprog)-4 )
SELECT #CV

SQL: Convert Data For One Column To Multiple Columns

I have tried looking for a solution to my problem using things such as Pivot, but they do not seem to give the output for which I am looking as they appear to map specific values in a row to a column.
I have two columns, the first which contains a "key" field and the second which is changing data. e.g.
╔══════════╦══════════════════╗
║ Project ║ Location ║
╠══════════╬══════════════════╣
║ ProjectA ║ \\Server1\Share1 ║
║ ProjectA ║ \\Server2\Share1 ║
║ ProjectB ║ \\Server6\Share2 ║
║ ProjectB ║ \\Server1\Share2 ║
║ ProjectB ║ \\Server2\Share3 ║
║ ProjectC ║ \\Server8\Share2 ║
║ ProjectD ║ \\Server5\Share9 ║
║ ProjectD ║ \\ServerX\ShareY ║
╚══════════╩══════════════════╝
The output that I am trying to achieve is as follows:
╔══════════╦══════════════════╦══════════════════╦══════════════════╦═════════╦══════════╦═════════╗
║ Project ║ Column1 ║ Column2 ║ Column3 ║ Column4 ║ Column5 ║ ColumnX ║
╠══════════╬══════════════════╬══════════════════╬══════════════════╬═════════╬══════════╬═════════╣
║ ProjectA ║ \\Server1\Share1 ║ \\Server2\Share1 ║ NULL ║ NULL ║ NULL ║ ║
║ ProjectB ║ \\Server1\Share2 ║ \\Server2\Share3 ║ \\Server6\Share2 ║ NULL ║ NULL ║ ║
║ ProjectC ║ \\Server8\Share2 ║ NULL ║ NULL ║ NULL ║ NULL ║ ║
║ ProjectD ║ \\Server5\Share9 ║ \\ServerX\ShareY ║ NULL ║ NULL ║ NULL ║ ║
╚══════════╩══════════════════╩══════════════════╩══════════════════╩═════════╩══════════╩═════════╝
If there is no data for the column then it will be NULL.
The number of distinct values in the location column is dynamic and the desired output is a generic column name, with the distinct location value beside the corresponding Project value.
Hopefully someone can help me with this problem as it is driving me mad!
Thanks in advance.
Ninja
Warning:
This solution assume that it will be max 6 column, you can add more for example up to 20.
LiveDemo
Data:
CREATE TABLE #mytable(
Project VARCHAR(80) NOT NULL
,Location VARCHAR(160) NOT NULL
);
INSERT INTO #mytable(Project,Location) VALUES ('ProjectA','\\Server1\Share1');
INSERT INTO #mytable(Project,Location) VALUES ('ProjectA','\\Server2\Share1');
INSERT INTO #mytable(Project,Location) VALUES ('ProjectB','\\Server6\Share2');
INSERT INTO #mytable(Project,Location) VALUES ('ProjectB','\\Server1\Share2');
INSERT INTO #mytable(Project,Location) VALUES ('ProjectB','\\Server2\Share3');
INSERT INTO #mytable(Project,Location) VALUES ('ProjectC','\\Server8\Share2');
INSERT INTO #mytable(Project,Location) VALUES ('ProjectD','\\Server5\Share9');
INSERT INTO #mytable(Project,Location) VALUES ('ProjectD','\\ServerX\ShareY');
INSERT INTO #mytable(Project,Location) VALUES ('ProjectD','\\ServerX\ShareY');
Query:
WITH cte AS
(
SELECT Project, Location,
[rn] = RANK() OVER (PARTITION BY Project ORDER BY Location)
FROM #mytable
)
SELECT
Project
,Column1 = MAX(CASE WHEN rn = 1 THEN Location ELSE NULL END)
,Column2 = MAX(CASE WHEN rn = 2 THEN Location ELSE NULL END)
,Column3 = MAX(CASE WHEN rn = 3 THEN Location ELSE NULL END)
,Column4 = MAX(CASE WHEN rn = 4 THEN Location ELSE NULL END)
,Column5 = MAX(CASE WHEN rn = 5 THEN Location ELSE NULL END)
,Column6 = MAX(CASE WHEN rn = 6 THEN Location ELSE NULL END)
-- ....
-- ,ColumnX = MAX(CASE WHEN rn = X THEN Location ELSE NULL END)
FROM cte
GROUP BY Project;
EDIT:
Truly generic solution that uses Dynamic-SQL and generates Pivoted column list:
LiveDemo2
DECLARE #cols NVARCHAR(MAX),
#cols_piv NVARCHAR(MAX),
#query NVARCHAR(MAX)
,#max INT = 0;
SELECT #max = MAX(c)
FROM (
SELECT Project, COUNT(DISTINCT Location) AS c
FROM #mytable
GROUP BY Project) AS s;
SET #cols = STUFF(
(SELECT ',' + CONCAT('[',c.n, '] AS Column',c.n, ' ')
FROM ( SELECT TOP (1000) n = ROW_NUMBER() OVER (ORDER BY [object_id]) FROM sys.all_objects ORDER BY n)AS c(n)
WHERE c.n <= #max
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');
SET #cols_piv = STUFF(
(SELECT ',' + CONCAT('[',c.n, '] ')
FROM ( SELECT TOP (1000) n = ROW_NUMBER() OVER (ORDER BY [object_id]) FROM sys.all_objects ORDER BY n)AS c(n)
WHERE c.n <= #max
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');
-- SELECT #cols;
set #query = N'SELECT Project, ' + #cols + ' from
(
select Project, Location,
[rn] = RANK() OVER (PARTITION BY Project ORDER BY Location)
from #mytable
) x
pivot
(
max(Location)
for rn in (' + #cols_piv + ')
) p ';
-- SELECT #query;
EXEC [dbo].[sp_executesql]
#query;
Credit goes to #lad2025.
I am using Server2005 so these are the modifications that I had to make to get this to work for me:
Couldn't declare and assign a variable in the same line. #max INT = 0;
CONCAT function doesn't exist so replaced with + between the parameters. ', ' + '[' + convert(varchar, c.n) + '] AS Column' + c.n
c.n is of type bigint so I had to CONVERT it to VARCHAR - CONVERT(VARCHAR, c.n)
When generating the table on which to pivot, a GROUP BY needs to be provided otherwise the data appears in a strange column number.
Without GROUP BY
Without the GROUP BY as more data is in the initial #mytable the results display in columns that are not sequential.
╔══════════╦══════════════════╦══════════════════╦══════════════════╦══════╦══════════════════╦══════╗
║ Project ║ Col1 ║ Col2 ║ Col3 ║ Col4 ║ Col5 ║ Col6 ║
╠══════════╬══════════════════╬══════════════════╬══════════════════╬══════╬══════════════════╬══════╣
║ ProjectA ║ \\Server1\Share1 ║ \\Server2\Share1 ║ NULL ║ NULL ║ NULL ║ NULL ║
║ ProjectB ║ \\Server1\Share2 ║ \\Server2\Share3 ║ \\Server6\Share2 ║ NULL ║ NULL ║ NULL ║
║ ProjectC ║ \\Server8\Share2 ║ NULL ║ NULL ║ NULL ║ NULL ║ NULL ║
║ ProjectD ║ NULL ║ \\Server5\Share9 ║ NULL ║ NULL ║ \\ServerX\ShareY ║ NULL ║
╚══════════╩══════════════════╩══════════════════╩══════════════════╩══════╩══════════════════╩══════╝
With GROUP BY As it should be.
╔══════════╦══════════════════╦══════════════════╦══════════════════╦══════╦══════╦══════╗
║ Project ║ Col1 ║ Col2 ║ Col3 ║ Col4 ║ Col5 ║ Col6 ║
╠══════════╬══════════════════╬══════════════════╬══════════════════╬══════╬══════╬══════╣
║ ProjectA ║ \\Server1\Share1 ║ \\Server2\Share1 ║ NULL ║ NULL ║ NULL ║ NULL ║
║ ProjectB ║ \\Server1\Share2 ║ \\Server2\Share3 ║ \\Server6\Share2 ║ NULL ║ NULL ║ NULL ║
║ ProjectC ║ \\Server8\Share2 ║ NULL ║ NULL ║ NULL ║ NULL ║ NULL ║
║ ProjectD ║ \\Server5\Share9 ║ \\ServerX\ShareY ║ NULL ║ NULL ║ NULL ║ NULL ║
╚══════════╩══════════════════╩══════════════════╩══════════════════╩══════╩══════╩══════╝
Data:
CREATE TABLE #mytable(
Project VARCHAR(80) NOT NULL
,Location VARCHAR(160) NOT NULL
);
INSERT INTO #mytable(Project,Location) VALUES ('ProjectA','\\Server1\Share1');
INSERT INTO #mytable(Project,Location) VALUES ('ProjectA','\\Server2\Share1');
INSERT INTO #mytable(Project,Location) VALUES ('ProjectB','\\Server6\Share2');
INSERT INTO #mytable(Project,Location) VALUES ('ProjectB','\\Server1\Share2');
INSERT INTO #mytable(Project,Location) VALUES ('ProjectB','\\Server2\Share3');
INSERT INTO #mytable(Project,Location) VALUES ('ProjectC','\\Server8\Share2');
INSERT INTO #mytable(Project,Location) VALUES ('ProjectD','\\Server5\Share9');
INSERT INTO #mytable(Project,Location) VALUES ('ProjectD','\\ServerX\ShareY');
Query:
DECLARE #cols NVARCHAR(MAX),
#cols_piv NVARCHAR(MAX),
#query NVARCHAR(MAX)
,#max INT;
SELECT #max = MAX(c)
FROM (
SELECT Project, COUNT(DISTINCT Location) AS c
FROM #mytable
GROUP BY Project) AS s;
SET #cols = STUFF(
(SELECT ', ' + '[' + convert(varchar, c.n) + '] AS Column' + convert(varchar, c.n)
FROM ( SELECT TOP (1000) n = ROW_NUMBER() OVER (ORDER BY [object_id]) FROM sys.all_objects ORDER BY n)AS c(n)
WHERE c.n <= #max
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)')
,1,1,'');
SET #cols_piv = STUFF(
(SELECT ',' + '[' + convert(varchar, c.n) + ']'
FROM ( SELECT TOP (1000) n = ROW_NUMBER() OVER (ORDER BY [object_id]) FROM sys.all_objects ORDER BY n)AS c(n)
WHERE c.n <= #max
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)')
,1,1,'');
set #query = N'SELECT Project, ' + #cols + ' from
(
select Project, Location,
[rn] = RANK() OVER (PARTITION BY Project ORDER BY Location)
from #mytable
GROUP BY Project, Location
) x
pivot
(
max(Location)
for rn in (' + #cols_piv + ')
) p ';
EXEC [dbo].[sp_executesql]
#query;

Running Total until specific condition is true

I'm having a table representing the dealers cards and their rank. I'm now trying to make a query (as fast as possible) to set status on the game.
(As said before, only the dealer cards is shown)
W = Win
S = Stand
L = Loss
B = Blackjack (in two cards)
About the rules:
The dealer wins at 21, if it's in two cards its blackjack. If the rank is between 17 and 20 it's S = stand. Over 21 is a loss.
Ranks:
1 (ACE) - 1 or 11 rank. Counted as 11.
2-10 - 2-10 rank
11-13 (knight - king) - 10 rank
╔════╦══════╦════════╗
║ Id ║ Rank ║ Status ║
╠════╬══════╬════════╣
║ 1 ║ 1 ║ ║
║ 2 ║ 5 ║ ║
║ 3 ║ 8 ║ L ║ //24 = Loss
║ 4 ║ 3 ║ ║
║ 5 ║ 1 ║ ║
║ 6 ║ 7 ║ W ║ //21 = Win
║ 7 ║ 10 ║ ║
║ 8 ║ 1 ║ B ║ //21 = Blackjack
║ 9 ║ 10 ║ ║
╚════╩══════╩════════╝
I've tried to use a counter to check if it's blackjack and then I'm using a "RunningPoint" to check the sum of the cards.
I have now a solution bad it shows very bad performance when it's a lot of data. How would you do this and what can I do to optimize my query? When using more data I also need to use option (maxrecursion 0)
(When having 1 million rows it's not even possible to run this...)
My example: http://sqlfiddle.com/#!6/3855e/1
There's no efficient solution using plain SQL (including Windowed Aggregate Functons), at least nobody found one, yet :-)
Your recursive query performs bad because it's way too complicated, this is a simplified version:
Edit: Fixed the calculation (Fiddle)
WITH ctePoints AS
(
SELECT 1 AS id
,rank
,CASE
WHEN rank >= 10 THEN 10
WHEN rank = 1 THEN 11
ELSE rank
END AS Point
,1 AS Counter
FROM dbo.BlackJack
WHERE Id = 1
UNION ALL
SELECT t2.Id
,t2.rank
,CASE WHEN t1.Point < 17 THEN t1.Point ELSE 0 END
+ CASE
WHEN t2.rank >= 10 THEN 10
WHEN t2.rank = 1 THEN 11
ELSE t2.rank
END AS Point
,CASE WHEN t1.Point < 17 THEN t1.Counter + 1 ELSE 1 END AS Counter
FROM dbo.BlackJack AS t2
INNER JOIN ctePoints AS t1 ON t2.Id = t1.Id + 1
)
SELECT ctepoints.*
,CASE
WHEN Point < 17 THEN ''
WHEN Point < 20 THEN 'S'
WHEN Point > 21 THEN 'L'
WHEN Point = 21 AND Counter = 2 THEN 'B'
ELSE 'W'
END AS DealerStatus
FROM ctePoints
It's probably still too slow, because it processes row by row.
I usually use recursive SQL to replace cursor logic (because in my DBMS it's usually much faster) but a cursor update might actually be faster (Demo):
CREATE TABLE #BlackJack
(
id INT PRIMARY KEY CLUSTERED
,Rank INT
,DealerStatus CHAR(1)
);
insert into #BlackJack (Id, Rank)
values
(1, 1),(2, 5), (3, 8), (4, 3), (5, 1), (6, 7), (7, 10), (8, 1),(9, 10), (10, 10), (11,1);
DECLARE #Counter INT = 0
,#Point INT = 0
,#id int
,#Rank int
,#DealerStatus char(1)
DECLARE c CURSOR
FOR
SELECT id, Rank
FROM #BlackJack
ORDER BY id FOR UPDATE OF DealerStatus
OPEN c
FETCH NEXT FROM c INTO #id, #Rank
WHILE ##FETCH_STATUS = 0
BEGIN
SET #counter = #counter + 1
SET #Rank = CASE
WHEN #Rank >= 10 THEN 10
WHEN #Rank = 1 THEN 11
ELSE #Rank
END
SET #Point = #Point + #Rank
SET #DealerStatus = CASE
WHEN #Point < 17 THEN ''
WHEN #Point < 20 THEN 'S'
WHEN #Point > 21 THEN 'L'
WHEN #Point = 21 AND #Counter = 2 THEN 'B'
ELSE 'W'
END
IF #Point >= 17
BEGIN
UPDATE #BlackJack
SET DealerStatus = #DealerStatus
WHERE CURRENT OF c;
SET #Point = 0
SET #Counter = 0
END
FETCH NEXT FROM c INTO #id, #Rank
END
CLOSE c
DEALLOCATE c
SELECT * FROM #BlackJack ORDER BY id
Still #lad2025's "quirky update" is the fastest way to get the expected result, but it's using an undocumented feature and if a Service Pack breaks it there's no way to complain about it :-)
This solution is based on quirky update. More info here.
LiveDemo
Data and structures:
CREATE TABLE #BlackJack
(
id INT
,Rank INT
,running_total INT
,result NVARCHAR(100)
);
CREATE CLUSTERED INDEX IX_ROW_NUM ON #BlackJack(id);
insert into #BlackJack (Id, Rank)
values (1, 1),(2, 5), (3, 8), (4, 3), (5, 1),
(6, 7), (7, 10), (8, 1),(9, 10), (10, 10), (11,1);
Main query:
DECLARE #running_total INT = 0
,#number_of_cards INT = 0
,#prev_running_total INT = 0;
UPDATE #BlackJack
SET
#prev_running_total = #running_total
,#running_total = running_total = IIF(#running_total >= 20, 0, #running_total)
+ CHOOSE(Rank,11,2,3,4,5,6,7,8,9,10,10,10,10)
,result = CASE WHEN #running_total = 20 THEN 'S'
WHEN #running_total = 21 AND #number_of_cards = 2 THEN 'B'
WHEN #running_total = 21 THEN 'W'
WHEN #running_total > 21 THEN 'L'
ELSE NULL
END
,#number_of_cards = IIF(#prev_running_total >= 20, 0, #number_of_cards) + 1
FROM #BlackJack WITH(INDEX(IX_ROW_NUM))
OPTION (MAXDOP 1);
SELECT *
FROM #BlackJack
ORDER BY id;
Warning
If you use SQL Server < 2012 you need to replace IIF and CHOOSE with CASE. I don't check all Blackjack rules, only for provided sample. If something is wrong feel free to change CASE logic.
Second I extend base table BlackJack with auxiliary columns, but you can create any new table, if needed.
The key point is to read data sequentially based on clustered key ascending and do not allow parallel execution. Before you use it in production check how it behaves with large data set.

update table with values in related records

I have a table which must be with next structure:
╔════╦═══════╦════╦═════╗
║ id ║ a ║ c ║ b ║
╠════╬═══════╬════╬═════╣
║ 55 ║ 56;57 ║ ║ P25 ║
║ 56 ║ ║ 56 ║ 25 ║
║ 57 ║ ║ 57 ║ 25 ║
╚════╩═══════╩════╩═════╝
where:
1) record with id=55 is a parent record and
2) records with id=56, id=57 (listed in a column and separated with semicolon) are child records
At first table is next
╔════╦═══════╦════╦═════╗
║ id ║ a ║ c ║ b ║
╠════╬═══════╬════╬═════╣
║ 55 ║ 56;57 ║ ║ ║
║ 56 ║ ║ 56 ║ ║
║ 57 ║ ║ 57 ║ ║
╚════╩═══════╩════╩═════╝
so I must to update table such as first table
For this purpose I created next CTE
with My_CTE(PId, a, c, b, newC, inde) as
(
select
ST.PID, ST.a, ST.c, ST.b, res.C,
ind = case
when ST.a != ''
then (dense_rank() over(order by ST.a))
end
from STable as ST
outer APPLY
fnSplit(ST.a) as res
where (not(ST.a = '') or not(ST.c = ''))
)
UPDATE STable
Set b =
cte.inde
From STable as st
Join My_CTE as cte on st.PID = cte.PId;
GO
As a result I have table with next values
╔════╦═══════╦════╦═════╗
║ id ║ a ║ c ║ b ║
╠════╬═══════╬════╬═════╣
║ 55 ║ 56;57 ║ ║ 25 ║
║ 56 ║ ║ 56 ║ ║
║ 57 ║ ║ 57 ║ ║
╚════╩═══════╩════╩═════╝
So I need to set values in column b for children records.
Maybe it could be established in select statement of MyCTE?
Help please
I am not entirely sure if I understand your request correctly so apologies if I have misunderstood.
So you have already managed to get the result set to here
╔════╦═══════╦════╦═════╗
║ id ║ a ║ c ║ b ║
╠════╬═══════╬════╬═════╣
║ 55 ║ 56;57 ║ ║ 25 ║
║ 56 ║ ║ 56 ║ ║
║ 57 ║ ║ 57 ║ ║
╚════╩═══════╩════╩═════╝
Now you want this to look like this
╔════╦═══════╦════╦═════╗
║ id ║ a ║ c ║ b ║
╠════╬═══════╬════╬═════╣
║ 55 ║ 56;57 ║ ║ P25 ║
║ 56 ║ ║ 56 ║ 25 ║
║ 57 ║ ║ 57 ║ 25 ║
╚════╩═══════╩════╩═════╝
Please see if this script works for you.
IF OBJECT_ID(N'tempdb..#temp')>0
DROP TABLE #temp
IF OBJECT_ID(N'tempdb..#temp1')>0
DROP TABLE #temp1
CREATE TABLE #temp (id int, a varchar(100),c varchar(100),b varchar(100))
INSERT INTO #temp VALUES ('55','56;57',' ','25')
INSERT INTO #temp VALUES ('56',' ','56',' ')
INSERT INTO #temp VALUES ('57',' ','57',' ')
SELECT * FROM #temp t
SELECT y.id, fn.string AS a,y.b
INTO #temp1
FROM #temp AS y
CROSS APPLY dbo.fnParseStringTSQL(y.a, ';') AS fn
--SELECT * FROM #temp1
UPDATE t
SET t.b=CASE WHEN CHARINDEX(';',t.a)>0 THEN 'P'+t.b ELSE t1.b END
FROM #temp t
LEFT JOIN #temp1 t1
ON t.id = t1.a
--DROP TABLE #temp
SELECT * FROM #temp t
IF OBJECT_ID(N'tempdb..#temp')>0
DROP TABLE #temp
IF OBJECT_ID(N'tempdb..#temp1')>0
DROP TABLE #temp1
Function borrowed from this link
CREATE FUNCTION [dbo].[fnParseStringTSQL]
(
#string NVARCHAR(MAX),
#separator NCHAR(1)
)
RETURNS #parsedString TABLE (string NVARCHAR(MAX))
AS
BEGIN
DECLARE #position INT
SET #position = 1
SET #string = #string + #separator
WHILE CHARINDEX(#separator, #string, #position) <> 0
BEGIN
INSERT INTO #parsedString
SELECT SUBSTRING(
#string,
#position,
CHARINDEX(#separator, #string, #position) - #position
)
SET #position = CHARINDEX(#separator, #string, #position) + 1
END
RETURN
END
This is really not an ideal data structure, but the following will do it...
CREATE TABLE #STable
(
id int primary key clustered
, a varchar(500)
, c varchar(500)
, b varchar(500)
)
INSERT INTO #STable
(id, a, c, b)
VALUES (55, '56;57', '', '25')
, (56, '', '56', '')
, (57, '', '57', '')
/* >>>>> Get all parents <<<<< */
CREATE TABLE #folks
(
sno int identity(1,1)
, id int
, a varchar(500)
)
CREATE TABLE #family
(
parent int
, child int
)
INSERT INTO #folks
(id, a)
SELECT id, a
FROM #STable
WHERE a <> ''
DECLARE #NID int
, #XID int
, #parent int
, #Children varchar(500)
, #Child int
SELECT #NID = MIN(sno), #XID = MAX(sno)
FROM #folks
/* >>>>> Loop to figure out the children <<<<< */
WHILE #NID <= #XID
BEGIN
SELECT #parent = id, #Children = a
FROM #folks
WHERE sno = #NID
WHILE LEN(#Children) > 0
BEGIN
IF CHARINDEX(';', #Children) > 0
BEGIN
SET #Child = CAST(LEFT(#Children, CHARINDEX(';', #Children) -1) as int)
SET #Children = RIGHT(#Children, LEN(#Children) - CHARINDEX(';', #Children))
INSERT INTO #family
(parent, child)
VALUES (#parent, #Child)
END
ELSE
BEGIN
SET #Child = CAST(#Children AS INT)
SET #Children = ''
INSERT INTO #family
(parent, child)
VALUES (#parent, #Child)
END
END
SET #NID = #NID + 1
END
/* >>>>> Update <<<<< */
UPDATE c
SET b = p.b
FROM #family f
INNER JOIN #STable p
on f.parent = p.id
INNER JOIN #STable c
on f.child = c.id
SELECT *
FROM #STable
DROP TABLE #STable
DROP TABLE #folks
DROP TABLE #family

Use result of one query in another sql query

I have below table structure :
Table1
╔═════╦══════╦═════════════╦═════════════╗
║Col1 ║ Col2 ║ TableName ║ ColumnName ║
╠═════╬══════╬═════════════╬═════════════╣
║ 1 ║ abc ║ Table2 ║ column2 ║
║ 2 ║ xyz ║ ║ ║
║ 3 ║ pqr ║ Table1 ║ column1 ║
║ 4 ║ jbn ║ ║ ║
╚═════╩════════════════════╩═════════════╝
Table2 :
╔════════╦═════════╗
║Column1 ║ Column2 ║
╠════════╬═════════╣
║ 1 ║ A ║
║ 2 ║ B ║
║ 3 ║ C ║
║ 4 ║ D ║
╚════════╩═════════╝
Table3
╔════════╦═════════╗
║Column1 ║ Column2 ║
╠════════╬═════════╣
║ 1 ║ X ║
║ 2 ║ Y ║
║ 3 ║ Z ║
║ 4 ║ A ║
╚════════╩═════════╝
I want to write stored procedure which will select data from Table1 and data from another table depending upon value of column tableName and columnName in Table1.
I want data in following format:
╔═════╦═════╦════════╗
║Col1 ║ Col2║ List ║
╠═════╬═════╬════════╣
║ 1 ║ abc ║A,B,C,D ║
║ 2 ║ xyz ║ ║
║ 3 ║ pqr ║1,2,3,4 ║
║ 4 ║ jbn ║ ║
╚═════╩═════╩════════╝
Try temporary table .
look at here : http://www.sqlteam.com/article/temporary-tables
You will need a dynamic sql to get such a select. Check out the link http://www.mssqltips.com/sqlservertip/1160/execute-dynamic-sql-commands-in-sql-server/
EDIT:
The following code should do the trick.
I have assumed that the column Col1 in Table1 is of type int.
I have used Temp table to generate the required table. You can replace it will your table as per your convenience. Also I have used #table1 which you can replace with your Table1.
Also this might not be very good in terms of performance but this is the best I could come up with right now.
declare #count int, #Query VARCHAR(5000), #counter int, #tableName VARCHAR(50), #ColumnName VARCHAR(50), #Col1 INT, #Col2 VARCHAR(50)
select #count = count(0) from #table1
SET #counter = 1
CREATE TABLE #table4
(
Col1 INT,
Col2 VARCHAR(50),
List VARCHAR(50)
)
WHILE #counter <= #count
BEGIN
SELECT #tableName = TableName, #ColumnName = columnName, #Col1 = Col1, #Col2 = Col2 FROM #Table1 WHERE Col1 = #counter
SELECT #Query = 'INSERT INTO #table4 (Col1 , Col2) VALUES (' + CONVERT(varchar(50),#Col1) + ', ''' + #Col2 + ''')'
EXEC (#Query)
SELECT #Query = ''
IF ISNULL(#tableName, '') != '' AND ISNULL(#ColumnName, '') != ''
BEGIN
SELECT #Query = 'UPDATE #table4 SET LIST = STUFF((SELECT '','' + CONVERT(VARCHAR(50), ' + #ColumnName + ') FROM ' + #tableName + ' FOR XML PATH('''')),1,1,'''') WHERE Col1 = ' + CONVERT(varchar(50),#Col1)
EXEC (#Query)
END
SET #counter = #counter + 1
END
SELECT * FROM #table4
Hope this helps
Answer edited for incorporating n no of rows
CODE
drop table #temp1
declare #tabletemp as varchar (10)
declare #columntemp as varchar (10)
Declare #sqlTemp NVARCHAR(400)
declare #counter1 as int
declare #counter2 as int
select #counter2 = 1
select name,cname into #temp1 from test1
select #counter1 = COUNT(*) from #temp1
while (#counter2<= #counter1)
begin
SET #tabletemp = (SELECT MIN(name) FROM #temp1)
select #columntemp = (select min(cname) from #temp1 where #temp1.name = #tabletemp)
set #sqlTemp='select '+#columntemp +' from '+#tabletemp
exec(#sqlTemp)
delete from #temp1 where name = #tabletemp and cname = #columntemp
select #counter1 = COUNT(*) from #temp1
end
RESULT
select * from test1
name cname
table1 column1
test2 colname
table1 test
test2 name
select column1 from table1
column1
qwer
asdff
zxcvb
qwer
asdff
zxcvb
select colname from test2
colname
testing
wer
ewrth
sfsf
testing
wer
ewrth
sfsf
select test from table1
--got error message inavlid column nae 'test' as this column does not exist
select name from test2
name
table1
table1
table3
table2
table1
table1
table3
table2