November 4, 2016 at 8:13 am
Hello all,
I have a table that I need to split up the records into multiple records by and Integer field. Here is what the table is like and what it should look like.
Thanks
/*** This is the Original Table that needs to be split into multiple records ***/
CREATE TABLE TestTable(SalesOrder VARCHAR(7), TotalMin INT)
INSERT INTO dbo.TestTable
( SalesOrder, TotalMin )
VALUES ('0164641', 1440 ),
('0191008', 737)
/*** This is the what the Final results should look like. ***/
CREATE TABLE FinalTable (SalesOrderNo VARCHAR(7),TotalMin INT)
INSERT INTO FinalTable
( SalesOrderNo, TotalMin )
VALUES ( '0164641', 360 ),
('0164641', 360),
('0164641',360),
('0164641',360),
('0191008', 360),
('0191008', 360),
('0191008',17)
November 4, 2016 at 8:45 am
You'll need to join to a numbers table
WITH
E(n) AS(
SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0))E(n)
),
E2(n) AS(
SELECT a.n FROM E a, E b
),
E4(n) AS(
SELECT a.n FROM E2 a, E2 b
),
cteTally(n) AS(
SELECT 0 UNION ALL
SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) n
FROM E4
)
SELECT t.SalesOrder,CASE WHEN c.n * 360 <= t.TotalMin THEN 360 ELSE t.TotalMin % 360 END AS TotalMin
FROM dbo.TestTable t
INNER JOIN cteTally c ON c.n BETWEEN 1 AND (t.TotalMin+359)/360;
____________________________________________________
Deja View - The strange feeling that somewhere, sometime you've optimised this query before
How to get the best help on a forum
http://www.sqlservercentral.com/articles/Best+Practices/61537November 4, 2016 at 8:51 am
Here's a solution using a table of numbers (you can create your own or use a virtual one) and my favorite TSQL word CASE. 🙂 Coffee hasn't kicked in and I am way down on sleep but I am certain there are other solutions, likely more efficient/cleaner.
;WITH a AS (
SELECT SalesOrder, TotalMin, TotalMin/360.0 AS BatchCount, TotalMin%360 AS Remainder
FROM dbo.#TestTable)
SELECT SalesOrder,
CASE WHEN Remainder = 0 OR (Remainder > 0 AND n.Number < BatchCount) THEN 360
ELSE Remainder
END AS TotalMin
-- , * --so you can see how it works :-D
FROM a INNER JOIN KGBTools.dbo.Numbers n
ON n.number <= CASE WHEN Remainder = 0.0 THEN a.BatchCount
ELSE a.BatchCount + 1
END
Best,
Kevin G. Boles
SQL Server Consultant
SQL MVP 2007-2012
TheSQLGuru on googles mail service
November 4, 2016 at 9:01 am
Thanks Mark
November 4, 2016 at 9:01 am
Hello
What is the Kgb.Numbers table?
November 4, 2016 at 9:05 am
Hello Guys,
Thanks for the posts. Both worked great!
Thank you 🙂
November 4, 2016 at 9:12 am
Both Mark's and Kevin's solutions use a non-SARGable join. Here is a version where the join is SARGable. It also allows for variable length ranges. If you do set up variable length ranges, you have to make sure that they are contiguous and that they don't overlap.
;
-- Set up a small tally table.
WITH Tally(n) AS (
SELECT ROW_NUMBER() OVER(ORDER BY @@VERSION)
FROM ( VALUES(1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) v(n)
)
-- Set up the ranges. Here the ranges are equal, but they don't have to be.
, Ranges AS (
SELECT (n-1)*360 AS start_val, n*360 AS end_val
FROM Tally
)
-- Allows for variable lengths of ranges.
SELECT t.SalesOrder, CASE WHEN t.TotalMin < r.end_val THEN t.TotalMin % r.start_val ELSE r.end_val - r.start_val END
FROM #TestTable t
INNER JOIN Ranges r
ON t.TotalMin > r.start_val
;
Drew
J. Drew Allen
Business Intelligence Analyst
Philadelphia, PA
November 4, 2016 at 9:12 am
Chuck S (11/4/2016)
HelloWhat is the Kgb.Numbers table?
A Numbers table of course! 😛
1-n with clustered index on that column. Gets you the same thing as the virtual one using the nested CTEs that someone else posted in their example.
Lots of articles and examples on the web and here on SSC.com about what you can do with them. Most commonly used feature is probably delimited string splitting. Search this website for DelimitedSplit8K.
Best,
Kevin G. Boles
SQL Server Consultant
SQL MVP 2007-2012
TheSQLGuru on googles mail service
November 4, 2016 at 9:33 am
TheSQLGuru (11/4/2016)
Chuck S (11/4/2016)
HelloWhat is the Kgb.Numbers table?
A Numbers table of course! 😛
1-n with clustered index on that column. Gets you the same thing as the virtual one using the nested CTEs that someone else posted in their example.
Lots of articles and examples on the web and here on SSC.com about what you can do with them. Most commonly used feature is probably delimited string splitting. Search this website for DelimitedSplit8K.
Thanks for you help. 🙂
November 4, 2016 at 9:35 am
drew.allen (11/4/2016)
Both Mark's and Kevin's solutions use a non-SARGable join. Here is a version where the join is SARGable. It also allows for variable length ranges. If you do set up variable length ranges, you have to make sure that they are contiguous and that they don't overlap.
;
-- Set up a small tally table.
WITH Tally(n) AS (
SELECT ROW_NUMBER() OVER(ORDER BY @@VERSION)
FROM ( VALUES(1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) v(n)
)
-- Set up the ranges. Here the ranges are equal, but they don't have to be.
, Ranges AS (
SELECT (n-1)*360 AS start_val, n*360 AS end_val
FROM Tally
)
-- Allows for variable lengths of ranges.
SELECT t.SalesOrder, CASE WHEN t.TotalMin < r.end_val THEN t.TotalMin % r.start_val ELSE r.end_val - r.start_val END
FROM #TestTable t
INNER JOIN Ranges r
ON t.TotalMin > r.start_val
;
Drew
Thanks Drew! 🙂
November 4, 2016 at 9:42 am
Well done Drew. Especially nice given the added flexibility. 2 very minor improvements:
WITH Tally(n) AS (
--more efficient "small" tally table :)
SELECT n
FROM ( VALUES(1), (2), (3), (4), (5), (6), (7), (8), (9), (10)) v(n)
)
-- Set up the ranges. Here the ranges are equal, but they don't have to be.
, Ranges AS (
SELECT (n-1)*360 AS start_val, n*360 AS end_val
FROM Tally
)
-- Allows for variable lengths of ranges.
--shortcut the CASE for the most frequent operation
SELECT t.SalesOrder,
CASE WHEN t.TotalMin >= r.end_val THEN r.end_val - r.start_val ELSE t.TotalMin % r.start_val END
FROM dbo.#TestTable t
INNER JOIN Ranges r
ON t.TotalMin > r.start_val;
Best,
Kevin G. Boles
SQL Server Consultant
SQL MVP 2007-2012
TheSQLGuru on googles mail service
November 4, 2016 at 9:55 am
TheSQLGuru (11/4/2016)
Well done Drew. Especially nice given the added flexibility. 2 very minor improvements:
Thanks. I did consider including the first improvement, but decided to go with the less efficient one in case the OP had never seen a tally table.
Drew
J. Drew Allen
Business Intelligence Analyst
Philadelphia, PA
November 4, 2016 at 10:45 am
Another way (building off Drew and Kevin's excellent solutions) would be like this:
WITH iTally(n) AS
(
SELECT n
FROM ( VALUES(1), (2), (3), (4), (5), (6), (7), (8), (9), (10)) v(n)
)
SELECT
SalesOrder,
CASE WHEN n <= TotalMin/360 THEN 360 ELSE ISNULL(NULLIF(TotalMin%360,0),360) END
FROM dbo.TestTable
CROSS JOIN iTally
WHERE n <= (TotalMin/360 + SIGN(TotalMin%360));
This won't be any faster but, with a perm tally table you can easily turn this into an indexed view:
CREATE VIEW dbo.xxx WITH SCHEMABINDING AS
SELECT
SalesOrder,
xx = CASE WHEN n <= TotalMin/360 THEN 360 ELSE ISNULL(NULLIF(TotalMin%360,0),360) END
FROM dbo.TestTable
CROSS JOIN dbo.tally
WHERE n <= (TotalMin/360 + SIGN(TotalMin%360));
-- WHERE n BETWEEN 0 AND (TotalMin/360 + SIGN(TotalMin%360)); -- for perm tally tables that start at 0
GO
-- Itzik Ben-Gan 2001
Viewing 13 posts - 1 through 12 (of 12 total)
You must be logged in to reply to this topic. Login to reply