Remove 'cross' duplicate result for double cross apply - sql

I have the following data in a column:
5;ABC|1;XYZ
I would like to split the value on the '|' delimiter and then split every result on the ';' delimiter.
I have the following query, but unfortenately it gives me (sort of) duplicate results.
DECLARE #MyTable TABLE ( Code VARCHAR(100) )
INSERT INTO #MyTable
VALUES ( '5;ABC|1;XYZ' );
WITH Query AS
(
SELECT T1.RowNum, SubSplit.Value FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY Code) as RowNum, Split.Value FROM #MyTable
CROSS APPLY SplitString(Code, '|') AS Split
) T1
CROSS APPLY SplitString(Value, ';') AS SubSplit
)
SELECT q1.Value AS [Left], q2.Value AS [Right] FROM Query q1
INNER JOIN Query q2 ON q1.RowNum = q2.RowNum AND q1.Value <> q2.Value
The result is:
But what I would like is:
How can I achieve this?
Edit
For completeness, this is the SplitString function I use:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION SplitString
(
-- Add the parameters for the function here
#input varchar(8000),
#delimiter varchar(1)
)
RETURNS TABLE
AS
RETURN
(
WITH cte AS
(
SELECT 0 a, 1 b
UNION ALL
SELECT b, CHARINDEX(#delimiter, #input, b) + LEN(#delimiter)
FROM CTE
WHERE b > a
)
SELECT SUBSTRING(#input, a,
CASE WHEN b > LEN(#delimiter)
THEN b - a - LEN(#delimiter)
ELSE LEN(#input) - a + 1 END) Value
FROM cte WHERE a > 0
)
GO

Add another ROW_NUMBER in the outer query:
SQL Fiddle
Query 1:
DECLARE #MyTable TABLE ( Code VARCHAR(100) )
INSERT INTO #MyTable
VALUES ( '5;ABC|1;XYZ|6;HXS|7;GGH' )
;WITH Query AS
(
SELECT T1.RowNum, SubSplit.Value,
ROW_NUMBER() OVER (PARTITION BY T1.RowNum ORDER BY SubSplit.Value) as RowNum1
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY Code) as RowNum, Split.Value FROM #MyTable
CROSS APPLY SplitString(Code, '|') AS Split
) T1
CROSS APPLY SplitString(Value, ';') AS SubSplit
)
SELECT q1.Value AS [Left], q2.Value AS [Right] FROM Query q1
INNER JOIN Query q2 ON q1.RowNum = q2.RowNum AND q1.RowNum1 = 1 AND q2.RowNum1 = 2
Results:
| Left | Right |
|------|-------|
| 5 | ABC |
| 1 | XYZ |
| 6 | HXS |
| 7 | GGH |
Update:
The use of the ROW_NUMBER() in the outer query will only work if the Left is less then Right when diong the string comparison. It will not work correctly for the '6;123' value. Therefore, there is a better approach by using an enhanced SplitString function as per below:
SQL Fiddle
MS SQL Server 2014 Schema Setup:
CREATE FUNCTION SplitString
(
-- Add the parameters for the function here
#input varchar(8000),
#delimiter varchar(1)
)
RETURNS TABLE
AS
RETURN
(
WITH cte AS
(
SELECT 0 a, 1 b, 0 rn
UNION ALL
SELECT b, CHARINDEX(#delimiter, #input, b) + LEN(#delimiter), rn + 1
FROM CTE
WHERE b > a
)
SELECT SUBSTRING(#input, a,
CASE WHEN b > LEN(#delimiter)
THEN b - a - LEN(#delimiter)
ELSE LEN(#input) - a + 1 END) Value,
rn
FROM cte WHERE a > 0
)
Query 1:
DECLARE #MyTable TABLE ( Code VARCHAR(100) )
INSERT INTO #MyTable
VALUES ( '5;ABC|1;XYZ|6;123|7;GGH' )
;WITH Query AS
(
SELECT T1.RowNum, SubSplit.Value,
SubSplit.rn as RowNum1
FROM
(
SELECT Split.rn as RowNum, Split.Value FROM #MyTable
CROSS APPLY SplitString(Code, '|') AS Split
) T1
CROSS APPLY SplitString(Value, ';') AS SubSplit
)
SELECT q1.Value AS [Left], q2.Value AS [Right] FROM Query q1
INNER JOIN Query q2 ON q1.RowNum = q2.RowNum AND q1.RowNum1 = 1 AND q2.RowNum1 = 2
Results:
| Left | Right |
|------|-------|
| 5 | ABC |
| 1 | XYZ |
| 6 | 123 |
| 7 | GGH |

Related

How to Generate 2 Levels Loop

If I have 2 variables
A = 2
B = 3
I want to generate result like this
A | B | Text
1 | 1 | Text1
1 | 2 | Text2
1 | 3 | Text3
2 | 1 | Text4
2 | 2 | Text5
2 | 3 | Text6
I try to Google and can achieved 1 level with this query
declare #start int = 1
declare #end int = 3
;with numcte
AS
(
SELECT #start as [SEQUENCE]
UNION all
SELECT [SEQUENCE] + 1
FROM numcte WHERE [SEQUENCE] < #end
)
SELECT [SEQUENCE], 'text' + CAST([SEQUENCE] as varchar) as [text] FROM numcte
How can I achieve 2 levels loop?
One rather simple method is:
select a.a, b.b, concat('text', row_number() over (order by a, b))
from (values (1), (2)) a(a) cross join
(values (1), (2), (3)) b(b);
Or if you really want to declare variables:
declare #a int = 2;
declare #b int = 3;
with n as (
select 1 as n union all
select n + 1
from n
where n < #a or n < #b
)
select na.n as a, nb.n as b, concat('text', row_number() over (order by na.n, nb.n))
from n na join
n nb
on na.n <= #a and nb.n <= #b;
Here is a db<>fiddle.
Use a numbers table (many examples you can use by searching). One way to produce what is likely a vastly simplified example is:
with cte as (
select 1 as num
union all select num + 1 from cte where num < 3 )
select cte.num, cte2.num from cte
cross join cte as cte2
where cte.num in (1, 2)
order by cte.num, cte2.num
;
Work through that - it may look daunting. Start thinking in terms of sets! Fiddle

Split string into multiple rows with multiple columns in paired

I have a table as below:
Order No | Customers | Amount
---------+----------------------+-------------
1 | Briant~~Luck | 23~~2122
2 | Mike~~Lee~~David | 10~~200~37
3 | Stak | 100
With format, each customer has one value in Amount.
I'm trying to figure out how to expand the ~~ delimited values to populate a new customers table, which should look like this:
Order No | Customer | Amount
---------+----------------------+---------
1 | Briant | 23
1 | Luck | 2122
2 | Mike | 10
2 | Lee | 200
2 | David | 37
3 | Stak | 100
How can I do?
Any solution in SQL query, function or cursor is appreciated.
Thanks
I think you could store data as your expected result structure. It is much better.
Btw you could use a split function to get your output
DECLARE #SampleData AS TABLE
(
OrderNo int,
Customers varchar(200),
Amount varchar(200)
)
INSERT INTO #SampleData
(
OrderNo,
Customers,
Amount
)
VALUES
( 1, 'Briant~~Luck','23~~2122'),
( 2, 'Mike~~Lee~~David','10~~200~~37'),
( 3, 'Stak','100')
SELECT sd.OrderNo, c.[Value] AS Customer, a.[Value] AS Amount
FROM #SampleData sd
CROSS APPLY
(
SELECT Pos, Value
FROM [dbo].[SplitString](sd.Customers,'~~')
) c
CROSS APPLY
(
SELECT Pos, Value
FROM [dbo].[SplitString](sd.Amount,'~~')
) a
WHERE c.Pos = a.Pos
ORDER BY sd.OrderNo
Split function
CREATE FUNCTION [dbo].[SplitString] (#Text varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
Select Pos = Row_Number() over (Order By (Select null))
,Value = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>'+ Replace(#Text,#Delimiter,'</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
);
Demo link: http://rextester.com/XRX32958
This solution uses XML, CROSS APPLY & ROW_NUMBER to deconstruct the '~~' seperated fields.
It doesn't require a UDF or the STRING_SPLIT function from SQL Server 2016.
-- Using a table variable for the test
declare #Orders table ([Order No] int, Customers varchar(30), Amount varchar(30));
insert into #Orders ([Order No], Customers, Amount) values
(1,'Briant~~Luck','23~~2122'),
(2,'Mike~~Lee~~David','10~~200~~37'),
(3,'Stak','100');
SELECT C.[Order No], C.Customer, A.Amount
FROM
(
SELECT
[Order No],
row_number() over (partition by [Order No] order by (select 1)) as rn,
Customers.Name.value('.', 'VARCHAR(100)') AS Customer
FROM (
SELECT [Order No], CAST ('<x>' + REPLACE(Customers, '~~', '</x><x>') + '</x>' AS XML) AS XCustomers
FROM #Orders
) AS Q1
CROSS APPLY Q1.XCustomers.nodes ('/x') AS Customers(Name)
) C
JOIN (
SELECT
[Order No],
row_number() over (partition by [Order No] order by (select 1)) as rn,
Amounts.Value.value('.', 'VARCHAR(100)') AS Amount
FROM (
SELECT [Order No], CAST ('<x>' + REPLACE(Amount, '~~', '</x><x>') + '</x>' AS XML) AS XAmounts
FROM #Orders
) AS Q1
CROSS APPLY Q1.XAmounts.nodes ('/x') AS Amounts(Value)
) A
ON (C.[Order No] = A.[Order No] AND C.RN = A.RN);
Or if you know there will be maximum 3 values in those strings.
Then the trick with PARSENAME could be used:
SELECT [Order No],
PARSENAME(REPLACE(Customers, '~~', '.'), v.n) as Customer,
PARSENAME(REPLACE(Amount, '~~', '.'), v.n) as Amount
FROM #Orders t
JOIN (VALUES (1),(2),(3)) AS v(n)
ON v.n <= (len(Customers) - len(replace(Customers, '~~', ','))+1);
If you are on SQL2016+ then try this:
drop table if exists dbo.Orders;
create table dbo.Orders (
ID int
, Customers varchar(100)
, Amount varchar(100)
);
insert into dbo.Orders (ID, Customers, Amount)
values (1,'Briant~~Luck', '23~~2122')
, (2, 'Mike~~Lee~~David', '10~~200~~37')
, (3, 'Stak', '100');
select
o.ID
, t01.value as Customer
, t02.value as Amount
from dbo.Orders o
outer apply (
select
ROW_NUMBER () over (order by o.Customers ASC) as Rbr
, t.value
from string_split (replace(o.Customers, '~~', '~'), '~') t
) t01
outer apply (
select
ROW_NUMBER () over (order by o.Amount ASC) as Rbr
, t.value
from string_split (replace(o.Amount, '~~', '~'), '~') t
) t02
where t01.Rbr = t02.Rbr
If you are on a version of SQL Server prior to 2016, you will need to create your own String Splitting function and reference that in your script. The version I use is as follows:
create function [dbo].[StringSplit]
(
#str nvarchar(4000) = ' ' -- String to split.
,#delimiter as nvarchar(1) = ',' -- Delimiting value to split on.
,#num as int = null -- Which value to return.
)
returns table
as
return
( -- Start tally table with 10 rows.
with n(n) as (select n from (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n(n))
-- Select the same number of rows as characters in #str as incremental row numbers.
-- Cross joins increase exponentially to a max possible 10,000 rows to cover largest #str length.
,t(t) as (select top (select len(#str) a) row_number() over (order by (select null)) from n n1,n n2,n n3,n n4)
-- Return the position of every value that follows the specified delimiter.
,s(s) as (select 1 union all select t+1 from t where substring(#str,t,1) = #delimiter)
-- Return the start and length of every value, to use in the SUBSTRING function.
-- ISNULL/NULLIF combo handles the last value where there is no delimiter at the end of the string.
,l(s,l) as (select s,isnull(nullif(charindex(#delimiter,#str,s),0)-s,4000) from s)
select rn as ItemNumber
,Item
from(select row_number() over(order by s) as rn
,substring(#str,s,l) as item
from l
) a
where rn = #num -- Return a specific value where specified,
or #num is null -- Or the everything where not.
)
And is used as follows. Note that I have split the outer apply into separate queries to avoid row duplication:
declare #t table(OrderNo int,Customers nvarchar(500),Amount nvarchar(500));
insert into #t values
(1,'Briant~~Luck','23~~2122')
,(2,'Mike~~Lee~~David','10~~200~~37')
,(3,'Stak','100');
with c as
(
select t.OrderNo
,c.ItemNumber
,c.Item as Customers
from #t t
outer apply dbo.StringSplit(replace(t.Customers,'~~','|'),'|',null) c
),a as
(
select t.OrderNo
,a.ItemNumber
,a.Item as Amount
from #t t
outer apply dbo.StringSplit(replace(t.Amount,'~~','|'),'|',null) a
)
select c.OrderNo
,c.Customers
,a.Amount
from c
join a
on(c.OrderNo = a.OrderNo
and c.ItemNumber = a.ItemNumber
)
order by a.OrderNo
,c.Customers;
Output:
+---------+-----------+--------+
| OrderNo | Customers | Amount |
+---------+-----------+--------+
| 1 | Briant | 23 |
| 1 | Luck | 2122 |
| 2 | David | 37 |
| 2 | Lee | 200 |
| 2 | Mike | 10 |
| 3 | Stak | 100 |
+---------+-----------+--------+
Here solution as plsql
declare
type t_array_text is table of varchar2(30);
type t_array_number is table of number;
array_text t_array_text := t_array_text();
array_number t_array_number := t_array_number();
i number := 1;
cursor c1 is
select * from deneme;
begin
FOR c in c1
LOOP
i := 1;
array_text := t_array_text();
array_number := t_array_number();
for rec in (
SELECT regexp_substr(c.customer, '[[:alpha:]]+', 1, level) a frOM dual
CONNECT BY level<=regexp_count(c.customer,'~~')+1)
loop
array_text.extend();
array_text (i) := rec.a;
i := i + 1;
end loop;
i := 1;
for rec in (
SELECT regexp_substr(c.amount, '[0-9]+', 1, level) a frOM dual
CONNECT BY level<=regexp_count(c.amount,'~~')+1)
loop
array_number.extend();
array_number (i) := rec.a;
i := i + 1;
end loop;
for y in 1..array_text.count loop
dbms_output.put_line (c.order_no || ' ' || array_text(y) || ' ' || array_number(y));
end loop;
END LOOP;
end;
result as follows:
1 Briant 23
1 Luck 2122
2 Mike 10
2 Lee 200
2 David 37
3 Stak 10

SQL Server - Complex Dynamic Pivot multiple columns

FYI, this question is already answered but I have some new requirements which is very complex to implement so, I am posting it as a new question instead of editing the old question: (Previous Question)
I have two tables "Controls" and "ControlChilds" (in the ControlChilds table we have added a new column called ControlChildComments which we need to show in PIVOT output)
Parent Table Structure:
Create table Controls(
ProjectID Varchar(20) NOT NULL,
ControlID INT NOT NULL,
ControlCode Varchar(2) NOT NULL,
ControlPoint Decimal NULL,
ControlScore Decimal NULL,
ControlValue Varchar(50)
)
Sample Data
ProjectID | ControlID | ControlCode | ControlPoint | ControlScore | ControlValue
P001 1 A 30.44 65 Invalid
P001 2 C 45.30 85 Valid
Child Table Structure:
Create table ControlChilds(
ControlID INT NOT NULL,
ControlChildID INT NOT NULL,
ControlChildValue Varchar(200) NULL,
ControlChildComments Varchar(200) NULL
)
Sample Data
ControlID | ControlChildID | ControlChildValue | ControlChildComments
1 100 Yes Something
1 101 No NULL
1 102 NA Others
1 103 Others NULL
2 104 Yes New one
2 105 SomeValue NULL
Based on my previous question (Previous Question) I got this output (You can refer to the PIVOT queries which produces this output in the answer given by #bluefeet. Thanks again #bluefeet.)
But now my requirement is changed and I need ControlChildComments after each Child values. For example, A_Child1, A_Child1Comments, A_Child2, A_Child2Comments etc...
Another tricky thing is I need to show the comments only when they are not null otherwise I shouldn't show the column. For example, in this case, it should be like this:
A_Child1, A_Child1Comments, A_Child2, A_Child3, A_Child3Comments, A_Child4, C_Child1, C_Child1Comments, C_Child2
Is this possible? I tried lot of things but the results are not accurate.
Since you now have multiple columns in your ControlChilds table that you need to PIVOT, you will need to use the similar method of unpivoting them first that you applied with the Controls table.
You will need to unpivot both the ChildControlValue and ChildControlComments using code similar to:
select
projectId,
col = ControlCode+'_'+subCol+cast(seq as varchar(10)),
value
from
(
select c.ProjectId,
c.ControlCode,
cc.ControlChildValue,
cc.ControlChildComments,
row_number() over(partition by c.ProjectId, c.ControlCode
order by cc.ControlChildId) seq
from controls c
inner join controlchilds cc
on c.controlid = cc.controlid
) d
cross apply
(
select 'ChildValue', ControlChildValue union all
select 'ChildComments', ControlChildComments
) c (subCol, value);
See SQL Fiddle with Demo. This gets your data in the format:
| PROJECTID | COL | VALUE |
|-----------|------------------|-----------|
| P001 | A_ChildValue1 | Yes |
| P001 | A_ChildComments1 | Something |
| P001 | A_ChildValue2 | No |
| P001 | A_ChildComments2 | (null) |
| P001 | A_ChildValue3 | NA |
You then use this code in your existing query:
select ProjectId,
A_ControlPoint, A_ControlScore, A_ControlValue,
A_ChildValue1, A_ChildComments1, A_ChildValue2,
A_ChildComments2, A_ChildValue3, A_ChildComments3,
A_ChildValue4, A_ChildComments4,
C_ControlPoint, C_ControlScore, C_ControlValue,
C_Child1, C_Child2
from
(
select
ProjectId,
col = ControlCode +'_'+col,
val
from
(
select
c.ProjectId,
c.ControlCode,
c.ControlPoint,
c.ControlScore,
c.ControlValue
from controls c
) d
cross apply
(
select 'ControlPoint', cast(controlpoint as varchar(10)) union all
select 'ControlScore', cast(ControlScore as varchar(10)) union all
select 'ControlValue', ControlValue
) c (col, val)
union all
select
projectId,
col = ControlCode+'_'+subCol+cast(seq as varchar(10)),
value
from
(
select c.ProjectId,
c.ControlCode,
cc.ControlChildValue,
cc.ControlChildComments,
row_number() over(partition by c.ProjectId, c.ControlCode
order by cc.ControlChildId) seq
from controls c
inner join controlchilds cc
on c.controlid = cc.controlid
) d
cross apply
(
select 'ChildValue', ControlChildValue union all
select 'ChildComments', ControlChildComments
) c (subCol, value)
) src
pivot
(
max(val)
for col in (A_ControlPoint, A_ControlScore, A_ControlValue,
A_ChildValue1, A_ChildComments1, A_ChildValue2,
A_ChildComments2, A_ChildValue3, A_ChildComments3,
A_ChildValue4, A_ChildComments4,
C_ControlPoint, C_ControlScore, C_ControlValue,
C_Child1, C_Child2)
) piv;
See SQL Fiddle with Demo. Finally, you'll implement this in your dynamic SQL script:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + QUOTENAME(col)
from
(
select ControlCode,
col = ControlCode +'_'+col,
seq,
so
from controls
cross apply
(
select 'ControlPoint', 0, 0 union all
select 'ControlScore', 0, 1 union all
select 'ControlValue', 0, 2
) c (col, seq, so)
union all
select ControlCode,
col = ControlCode+'_'+subcol+cast(rn as varchar(10)),
rn,
so
from
(
select ControlCode,
row_number() over(partition by c.ProjectId, c.ControlCode
order by cc.ControlChildId) seq
from controls c
inner join controlchilds cc
on c.controlid = cc.controlid
) d
cross apply
(
select 'ChildValue', seq, 3 union all
select 'ChildComments', seq, 4
) c (subcol, rn, so)
) src
group by ControlCode, seq, col, so
order by ControlCode, seq, so
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ProjectId, ' + #cols + '
from
(
select ProjectId,
col = ControlCode +''_''+col,
val
from
(
select
c.ProjectId,
c.ControlCode,
c.ControlPoint,
c.ControlScore,
c.ControlValue
from controls c
) d
cross apply
(
select ''ControlPoint'', cast(controlpoint as varchar(10)) union all
select ''ControlScore'', cast(ControlScore as varchar(10)) union all
select ''ControlValue'', ControlValue
) c (col, val)
union all
select
projectId,
col = ControlCode+''_''+subCol+cast(seq as varchar(10)),
value
from
(
select c.ProjectId,
c.ControlCode,
cc.ControlChildValue,
cc.ControlChildComments,
row_number() over(partition by c.ProjectId, c.ControlCode
order by cc.ControlChildId) seq
from controls c
inner join controlchilds cc
on c.controlid = cc.controlid
) d
cross apply
(
select ''ChildValue'', ControlChildValue union all
select ''ChildComments'', ControlChildComments
) c (subCol, value)
) x
pivot
(
max(val)
for col in (' + #cols + ')
) p '
exec sp_executesql #query;
See SQL Fiddle with Demo. Both of these gives a result:
| PROJECTID | A_CONTROLPOINT | A_CONTROLSCORE | A_CONTROLVALUE | A_CHILDVALUE1 | A_CHILDCOMMENTS1 | A_CHILDVALUE2 | A_CHILDCOMMENTS2 | A_CHILDVALUE3 | A_CHILDCOMMENTS3 | A_CHILDVALUE4 | A_CHILDCOMMENTS4 | C_CONTROLPOINT | C_CONTROLSCORE | C_CONTROLVALUE | C_CHILDVALUE1 | C_CHILDCOMMENTS1 | C_CHILDVALUE2 | C_CHILDCOMMENTS2 |
|-----------|----------------|----------------|----------------|---------------|------------------|---------------|------------------|---------------|------------------|---------------|------------------|----------------|----------------|----------------|---------------|------------------|---------------|------------------|
| P001 | 30.44 | 65.00 | Invalid | Yes | Something | No | (null) | NA | Others | Others | (null) | 45.30 | 85.00 | Valid | Yes | New one | SomeValue | (null) |
Here is an example of a dynamic crosstab. Since you have multiple columns you would need to adjust the dynamic portion of this to suit.
if OBJECT_ID('Something') is not null
drop table Something
create table Something
(
ID int,
Subject1 varchar(50)
)
insert Something
select 10868952, 'NUR/3110/D507' union all
select 10868952, 'NUR/3110/D512' union all
select 10868952, 'NUR/4010/D523' union all
select 10868952, 'NUR/4010/HD20' union all
select 12345, 'asdfasdf'
declare #MaxCols int
declare #StaticPortion nvarchar(2000) =
'with OrderedResults as
(
select *, ROW_NUMBER() over(partition by ID order by Subject1) as RowNum
from Something
)
select ID';
declare #DynamicPortion nvarchar(max) = '';
declare #FinalStaticPortion nvarchar(2000) = ' from OrderedResults Group by ID order by ID';
with E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select #DynamicPortion = #DynamicPortion +
', MAX(Case when RowNum = ' + CAST(N as varchar(6)) + ' then Subject1 end) as Subject' + CAST(N as varchar(6)) + CHAR(10)
from cteTally t
where t.N <=
(
select top 1 Count(*)
from Something
group by ID
order by COUNT(*) desc
)
select #StaticPortion + #DynamicPortion + #FinalStaticPortion
--declare #SqlToExecute nvarchar(max) = #StaticPortion + #DynamicPortion + #FinalStaticPortion;
--exec sp_executesql #SqlToExecute

How to limit the selection in SQL Server by sum of a column?

Can I limit rows by sum of a column in a SQL Server database?
For example:
Type | Time (in minutes)
-------------------------
A | 50
B | 10
C | 30
D | 20
E | 70
...
And I want to limit the selection by sum of time. For example maximum of 100 minutes. Table must look like this:
Type | Time (in minutes)
-------------------------
A | 50
B | 10
C | 30
Any ideas? Thanks.
DECLARE #T TABLE
(
[Type] CHAR(1) PRIMARY KEY,
[Time] INT
)
INSERT INTO #T
SELECT 'A',50 UNION ALL
SELECT 'B',10 UNION ALL
SELECT 'C',30 UNION ALL
SELECT 'D',20 UNION ALL
SELECT 'E',70;
WITH RecursiveCTE
AS (
SELECT TOP 1 [Type], [Time], CAST([Time] AS BIGINT) AS Total
FROM #T
ORDER BY [Type]
UNION ALL
SELECT R.[Type], R.[Time], R.Total
FROM (
SELECT T.*,
T.[Time] + Total AS Total,
rn = ROW_NUMBER() OVER (ORDER BY T.[Type])
FROM #T T
JOIN RecursiveCTE R
ON R.[Type] < T.[Type]
) R
WHERE R.rn = 1 AND Total <= 100
)
SELECT [Type], [Time], Total
FROM RecursiveCTE
OPTION (MAXRECURSION 0);
Or if your table is small
SELECT t1.[Type],
t1.[Time],
SUM(t2.[Time])
FROM #T t1
JOIN #T t2
ON t2.[Type] <= t1.[Type]
GROUP BY t1.[Type],t1.[Time]
HAVING SUM(t2.[Time]) <=100

Transforming Table into different Table

I have a table like this:
RowID | ProductDescription1
-----------------------------------------------------
1 | 0296620300-0296620399;
2 | 0296620400-0296620499;0296620500-0296620599;
3 | 0296620600-0296620699;0296620700-0296620799;
I want to become like this:
NewRowID | Start | End | SourceRowID
--------------------------------------------------
1 | 0296620300 | 0296620399 | 1
2 | 0296620400 | 0296620499 | 2
3 | 0296620500 | 0296620599 | 2
4 | 0296620600 | 0296620699 | 3
5 | 0296620700 | 0296620799 | 3
Now I have a function that can do splitting stuff which returning table :
ALTER FUNCTION [dbo].[ufn_stg_SplitString]
(
-- Add the parameters for the function here
#myString varchar(500),
#deliminator varchar(10)
)
RETURNS
#ReturnTable TABLE
(
-- Add the column definitions for the TABLE variable here
[id] [int] IDENTITY(1,1) NOT NULL,
[part] [varchar](50) NULL
)
AS
BEGIN
Declare #iSpaces int
Declare #part varchar(50)
--initialize spaces
Select #iSpaces = charindex(#deliminator,#myString,0)
While #iSpaces > 0
Begin
Select #part = substring(#myString,0,charindex(#deliminator,#myString,0))
Insert Into #ReturnTable(part)
Select #part
Select #myString = substring(#mystring,charindex(#deliminator,#myString,0)+ len(#deliminator),len(#myString) - charindex(' ',#myString,0))
Select #iSpaces = charindex(#deliminator,#myString,0)
end
If len(#myString) > 0
Insert Into #ReturnTable
Select #myString
RETURN
END
I want to avoid using cursor if it's possible.
I am appreciated your comment/input.
First, this solution requires SQL Server 2005+. Second, at the bottom, I offer an alternate Split function which does not use a cursor. Third, here is a solution that does not rely on the values being of a specified length but instead that the delimiter is consistent:
Select Row_Number() Over ( Order By Z.PairNum ) As ItemNum
, Min(Case When Z.PositionNum = 1 Then Z.Value End) As [Start]
, Min(Case When Z.PositionNum = 2 Then Z.Value End) As [End]
, Z.RowId As SourceRowId
From (
Select T2.RowId, S.Value, T2.PairNum
, Row_Number() Over ( Partition By T2.RowId, T2.PairNum Order By S.Value ) As PositionNum
From (
Select T.RowId, S.Value
, Row_Number() Over ( Order By S.Value ) As PairNum
From MyTable As T
Cross Apply dbo.Split( T.ProductDescription, ';' ) As S
) As T2
Cross Apply dbo.Split( T2.Value, '-' ) As S
) As Z
Group By Z.RowId, Z.PairNum
And the Split function:
Create FUNCTION [dbo].[Split]
(
#DelimitedList nvarchar(max)
, #Delimiter nvarchar(2) = ','
)
RETURNS TABLE
AS
RETURN
(
With CorrectedList As
(
Select Case When Left(#DelimitedList, Len(#Delimiter)) <> #Delimiter Then #Delimiter Else '' End
+ #DelimitedList
+ Case When Right(#DelimitedList, Len(#Delimiter)) <> #Delimiter Then #Delimiter Else '' End
As List
, Len(#Delimiter) As DelimiterLen
)
, Numbers As
(
Select TOP (Len(#DelimitedList)) Row_Number() Over ( Order By c1.object_id ) As Value
From sys.objects As c1
Cross Join sys.columns As c2
)
Select CharIndex(#Delimiter, CL.list, N.Value) + CL.DelimiterLen As Position
, Substring (
CL.List
, CharIndex(#Delimiter, CL.list, N.Value) + CL.DelimiterLen
, CharIndex(#Delimiter, CL.list, N.Value + 1)
- ( CharIndex(#Delimiter, CL.list, N.Value) + CL.DelimiterLen )
) As Value
From CorrectedList As CL
Cross Join Numbers As N
Where N.Value < Len(CL.List)
And Substring(CL.List, N.Value, CL.DelimiterLen) = #Delimiter
)
SQL 2005/2008
with prods as
(
select 1 as RowID, '0296620300-0296620399;' AS ProductDescription1 union all
select 2 as RowID, '0296620400-0296620499;0296620500-0296620599;' AS ProductDescription1 union all
select 3 as RowID, '0296620600-0296620699;0296620700-0296620799;' AS ProductDescription1
)
select
ROW_NUMBER() OVER(ORDER BY RowId) as NewRowID,
LEFT(Part,10) AS Start, /*Might need charindex if they are not always 10 characters*/
RIGHT(Part,10) AS [End],
RowId as SourceRowID from prods
cross apply [dbo].[ufn_stg_SplitString] (ProductDescription1,';') p
Gives
NewRowID Start End SourceRowID
-------------------- ---------- ---------- -----------
1 0296620300 0296620399 1
2 0296620400 0296620499 2
3 0296620500 0296620599 2
4 0296620600 0296620699 3
5 0296620700 0296620799 3