Add null's for numerics using CTE in Sql Server 2005 - sql

I have some data as below
ID Data
1 a
1 2
1 b
1 1
2 d
2 3
2 r
Desired Output
ID Data
1 a
1 NULL
1 NULL
1 b
1 NULL
2 d
2 NULL
2 NULL
2 NULL
2 r
What the output is , for the numerics it is replaced with those many null values. E.g. for a numeric value of 2 , it will be 2 null values.
The ddl is as under
Declare #t table(ID int, data varchar(10))
Insert into #t
Select 1, 'a' union all select 1, '2' union all select 1, 'b' union all select 1, '1' union all select 2,'d' union all
select 2,'3' union all select 2, 'r'
select * from #t
Please give a CTE based solution. I already have the procedural approach but I need to have a feel of CTE based solution.
Solution I am using at present
CREATE FUNCTION [dbo].[SPLIT] (
#str_in VARCHAR(8000)
)
RETURNS #strtable TABLE (id int identity(1,1), strval VARCHAR(8000))
AS
BEGIN
DECLARE #tmpStr VARCHAR(8000), #tmpChr VARCHAR(5), #ind INT = 1, #nullcnt INT = 0
SELECT #tmpStr = #str_in
WHILE LEN(#tmpStr) >= #ind
BEGIN
SET #tmpChr = SUBSTRING(#tmpStr,#ind,1)
IF ISNUMERIC(#tmpChr) = 0
INSERT INTO #strtable SELECT #tmpChr
ELSE
WHILE #nullcnt < #tmpChr
BEGIN
INSERT INTO #strtable SELECT NULL
SET #nullcnt = #nullcnt + 1
END
SELECT #ind = #ind + 1, #nullcnt = 0
END
RETURN
END
GO
Invocation
SELECT * from dbo.SPLIT('abc2e3g')
I donot want to do it using function but a CTE solution.
Reason: I am learning CTE and trying to solve problems using it. Trying to come out of procedural/rBar approach.
Thanks

Setup:
declare #t table(UniqueID int identity(1,1), ID int, data varchar(10))
insert into #t
select 1, 'a' union all
select 1, '2' union all
select 1, 'b' union all
select 1, '1' union all
select 2, 'd' union all
select 2, '3' union all
select 2, 'r'
Query:
;with cte
as
(
select UniqueId, id, data, case when isnumeric(data) = 1 then cast(data as int) end Level
from #t
union all
select UniqueId, id, null, Level - 1
from cte
where Level > 0
)
select id, data
from cte
where Level is null or data is null
order by UniqueID, Level
Output:
id data
----------- ----------
1 a
1 NULL
1 NULL
1 b
1 NULL
2 d
2 NULL
2 NULL
2 NULL
2 r
I added UniqueId as there should be some kind of field that specifies order in the original table.

If you have a numbers table, this becomes quite simple.
;WITH Nbrs ( Number ) AS (
SELECT 1 UNION ALL
SELECT 1 + Number FROM Nbrs WHERE Number < 8000 )
SELECT
M.UniqueID, M.ID, Data
FROM
#t M
WHERE
ISNUMERIC(M.Data) = 0
UNION ALL
SELECT
M.UniqueID, M.ID, NULL
FROM
#t M
JOIN --the <= JOIN gives us (M.Data) rows
Nbrs N ON N.Number <= CASE WHEN ISNUMERIC(M.Data) = 1 THEN M.Data ELSE NULL END
ORDER BY
UniqueID
OPTION (MAXRECURSION 0)
Edit: JOIN was wrong way around. Oops.
Numbers CTE taken from here

Related

Optimizing the Sql Server 2019 Query [ About Categories with Id and ParentId Relation using single column in table]

How can i optimize the Sql Server Query
Table name is: Pro_itemmaster
Column name is: itm_Code
Test Data = Download Link
My Query take 17 seconds to complete
Query
; WITH CatItem AS(
SELECT
PIM.itm_Code AS Id
,CASE WHEN LEN(PIM.itm_Code) = 2 THEN NULL ELSE LEFT(PIM.itm_Code, LEN(PIM.itm_Code) - CHARINDEX('-',REVERSE(PIM.itm_Code))) END AS ParentId
,1 AS [Depth]
FROM
Pro_itemmaster AS PIM
WHERE
LEN(PIM.itm_Code) = 2
UNION ALL
SELECT
PIM.itm_Code AS Id
,CASE WHEN LEN(PIM.itm_Code) = 2 THEN NULL ELSE LEFT(PIM.itm_Code, LEN(PIM.itm_Code) - CHARINDEX('-',REVERSE(PIM.itm_Code))) END AS ParentId
,[CatItem].[Depth] + 1 AS [Depth]
FROM
[Pro_itemmaster] AS [PIM]
JOIN
[CatItem]
ON
CASE WHEN LEN(PIM.itm_Code) = 2 THEN NULL ELSE LEFT(PIM.itm_Code, LEN(PIM.itm_Code) - CHARINDEX('-',REVERSE(PIM.itm_Code))) END = CatItem.Id
)
SELECT * FROM CatItem
Query Execution Plan
I am able to reduce the time to 3 seconds by introducing a table variable.
The issue here is that I cannot modify the main table and add ParentId column so needed the work around.
DECLARE #DataTable TABLE (Id VARCHAR(60) PRIMARY KEY NOT NULL, ParentId VARCHAR(60))
INSERT INTO #DataTable
SELECT itm_Code
, CASE WHEN LEN(itm_Code) = 2 THEN NULL ELSE LEFT(itm_Code, LEN(itm_Code) - CHARINDEX('-',REVERSE(itm_Code))) END
FROM Pro_itemmaster
; WITH CatItem AS(
SELECT
PIM.Id
,PIM.ParentId
,1 AS [Depth]
FROM
#DataTable AS PIM
WHERE
LEN(PIM.Id) = 2
UNION ALL
SELECT
PIM.Id
,PIM.ParentId
,[CatItem].[Depth] + 1 AS [Depth]
FROM
#DataTable AS [PIM]
JOIN
[CatItem]
ON
PIM.ParentId = CatItem.Id
)
SELECT *
FROM CatItem
ORDER BY Id

How to traverse a path in a table with id & parentId?

Suppose I have a table like:
id | parentId | name
1 NULL A
2 1 B
3 2 C
4 1 E
5 3 E
I am trying to write a scalar function I can call as:
SELECT dbo.GetId('A/B/C/E') which would produce "5" if we use the above reference table. The function would do the following steps:
Find the ID of 'A' which is 1
Find the ID of 'B' whose parent is 'A' (id:1) which would be id:2
Find the ID of 'C' whose parent is 'B' (id:2) which would be id:3
Find the ID of 'E' whose parent is 'C' (id:3) which would be id:5
I was trying to do it with a WHILE loop but it was getting very complicated very fast... Just thinking there must be a simple way to do this.
CTE version is not optimized way to get the hierarchical data. (Refer MSDN Blog)
You should do something like as mentioned below. It's tested for 10 millions of records and is 300 times faster than CTE version :)
Declare #table table(Id int, ParentId int, Name varchar(10))
insert into #table values(1,NULL,'A')
insert into #table values(2,1,'B')
insert into #table values(3,2,'C')
insert into #table values(4,1,'E')
insert into #table values(5,3,'E')
DECLARE #Counter tinyint = 0;
IF OBJECT_ID('TEMPDB..#ITEM') IS NOT NULL
DROP TABLE #ITEM
CREATE TABLE #ITEM
(
ID int not null
,ParentID int
,Name VARCHAR(MAX)
,lvl int not null
,RootID int not null
)
INSERT INTO #ITEM
(ID,lvl,ParentID,Name,RootID)
SELECT Id
,0 AS LVL
,ParentId
,Name
,Id AS RootID
FROM
#table
WHERE
ISNULL(ParentId,-1) = -1
WHILE ##ROWCOUNT > 0
BEGIN
SET #Counter += 1
insert into #ITEM(ID,ParentId,Name,lvl,RootID)
SELECT ci.ID
,ci.ParentId
,ci.Name
,#Counter as cntr
,ch.RootID
FROM
#table AS ci
INNER JOIN
#ITEM AS pr
ON
CI.ParentId=PR.ID
LEFT OUTER JOIN
#ITEM AS ch
ON ch.ID=pr.ID
WHERE
ISNULL(ci.ParentId, -1) > 0
AND PR.lvl = #Counter - 1
END
select * from #ITEM
Here is an example of functional rcte based on your sample data and requirements as I understand them.
if OBJECT_ID('tempdb..#Something') is not null
drop table #Something
create table #Something
(
id int
, parentId int
, name char(1)
)
insert #Something
select 1, NULL, 'A' union all
select 2, 1, 'B' union all
select 3, 2, 'C' union all
select 4, 1, 'E' union all
select 5, 3, 'E'
declare #Root char(1) = 'A';
with MyData as
(
select *
from #Something
where name = #Root
union all
select s.*
from #Something s
join MyData d on d.id = s.parentId
)
select *
from MyData
Note that if you change the value of your variable the output will adjust. I would make this an inline table valued function.
I think I have it based on #SeanLange's recommendation to use a recursive CTE (above in the comments):
CREATE FUNCTION GetID
(
#path VARCHAR(MAX)
)
/* TEST:
SELECT dbo.GetID('A/B/C/E')
*/
RETURNS INT
AS
BEGIN
DECLARE #ID INT;
WITH cte AS (
SELECT p.id ,
p.parentId ,
CAST(p.name AS VARCHAR(MAX)) AS name
FROM tblT p
WHERE parentId IS NULL
UNION ALL
SELECT p.id ,
p.parentId ,
CAST(pcte.name + '/' + p.name AS VARCHAR(MAX)) AS name
FROM dbo.tblT p
INNER JOIN cte pcte ON
pcte.id = p.parentId
)
SELECT #ID = id
FROM cte
WHERE name = #path
RETURN #ID
END

In SQL , how to build a loop that copies a row number of times

could someone please help? My starting table looks like this with 2 fields:
Name Counter
dave 2
Joe 3
I want my result to look like this:
Name Counter
dave 1
dave 2
joe 1
joe 2
joe 3
Essentially creating n number of records base on the counter and starts at 1. I tried to do a loop using counter as a variable, but the code just runs nonstop.. could someone help?
A procedural SQL Server solution:
declare #input table
(
name nvarchar(100)
,wantedrows int
,processed bit
,id uniqueidentifier
);
declare #output table
(
name nvarchar(100)
,rownum int
);
insert into #input
select 'Dave',3,0,newid()
union
select 'Joe',2,0,newid();
while exists(select * from #input where processed = 0)
begin
declare #currentid uniqueidentifier = (select top 1 id from #input where processed = 0);
declare #currentwantedrows int = (select wantedrows from #input where id = #currentid);
declare #i int = 0;
while #i < #currentwantedrows
begin
insert into #output
select name,#i+1
from #input
where id = #currentid;
set #i = #i + 1;
end;
update #input set processed = 1 where id = #currentid;
end
select name,wantedrows from #input;
select * from #output;
You can use a number-table or following trick using a system view to build a sequence:
WITH Nums AS
(
SELECT n = ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_objects
)
SELECT Name, Counter = n
FROM Nums n CROSS JOIN Table1 t1
WHERE n BETWEEN 1 AND Counter
ORDER BY Name, Counter;
Demo
This view has only about 2000 rows, so if you need more you could use a number-table.
http://sqlperformance.com/2013/01/t-sql-queries/generate-a-set-1
( presuming SQL-Server )
Is a hundred copies enough?
create table #c (num)
insert into #c (num)
select 0 union
select 1 union
select 2 union
select 3 union
select 4 union
select 5 union
select 6 union
select 7 union
select 8 union
select 9
select T.Name, c1.num * 10 + c0.num + 1
from T, #c c1, #c c0
where c1.num * 10 + c0.num < T.Counter
drop table #c
You didn't say which version of Sybase. The old ones I've worked on didn't allow derived tables so I had to throw the values into a temp table. But you can see how to extend the idea. This may not be the best approach if this is something you need to do more than once though.

Get top number of rows anyway

My requirement is like select top 5 rows from sql, if it contains only 2 rows then by default it shows some text say 'no data' in remaining 3 rows. Same for all conditions.
For i.e. Select top 5 rows, but it contains only 3 rows then query will return
Row1
Row2
Row3
No Data
No Data
Please try:
select top 5 Col
from(
select 0 srt, Col from YourTable
union all
select 1 srt, 'No Data' Col union all
select 1 srt, 'No Data' Col union all
select 1 srt, 'No Data' Col union all
select 1 srt, 'No Data' Col union all
select 1 srt, 'No Data' Col
)x
order by srt
this will be much more dynamic
declare #t TABLE (id int,TerminalID varchar(15))
declare #top int
set #top = 5
declare #c int --Holds Total
set #c = 0
while (#c < #top) begin
insert into #t (id, TerminalID) values (#c, 'No Data')
set #c = #c + 1
end
select top 5 * from
(
select top 5 TerminalID from AccessLog-- [where colo0 = ???]
union all
select TerminalID from #t
) x
/* assuming the column width is 8 characters and datatype is varchar */
DECLARE #NoDataTable AS TABLE(column1 VARCHAR(8))
DECLARE #i AS INT
SET #i = 0
WHILE(#i<5)
BEGIN
insert into #NoDataTable (column1)
values('No Data');
set #i = #i+1
end
select top 5 *
from
(
select column1 from TestTable
union all
select column1 from #NoDataTable
) as T
WITH cte AS
(
SELECT YourColumn FROM YourTable
UNION ALL
SELECT TOP 5 NULL FROM YourTable
)
SELECT TOP 5 COALESCE(YourColumn, 'No Data')
FROM cte

Subtract all values from data

Table data look like this
id val
1 4
2 2
3 1
I want result of subtract valu of val field in one sql statement.
like it should be like 4-2-1 = 1 if order by id asc, 1-2-4 = -5 if order by id desc.
You can try this
DECLARE #Table TABLE(
ID INT,
Val INT
)
INSERT INTO #Table (ID,Val) SELECT 1, 4
INSERT INTO #Table (ID,Val) SELECT 2, 2
INSERT INTO #Table (ID,Val) SELECT 3, 1
SELECT SUM(Val * CASE WHEN RowID = 1 THEN 1 ELSE -1 END)
FROM (
SELECT *,
ROW_NUMBER() OVER (ORDER BY ID) RowID
FROM #Table
) sub
You can declare a variable and increment it in the select statement:
declare #sum float
select #sum = case when #sum is null then value else #sum - value end
from YourTable
order by id
select #sum
To reverse the subtraction order, change order by id to order by id desc.
if you want to use just the sql without temp tables or variables:
select fromid.val - sumid.val
from (
select val
from t
where id = (
select min(id)
from t
)
) fromid cross join (
select sum(val) as val
from t
where id > (
select min(id)
from t
)
) sumid