cte in sql server with temp table and split string - sql

I have table with two colums ID and rights. Rights column will keep 250 fixed characters in 0,1 form like '010111100000000....250 times'. Now I have to split the rights column string and get the results in temp table which will have the structure ID, Rights(0 or 1), Position(1 to 250). Suppose I have 5 rows initially then in temp table I will get 5*250 = 1250 rows.
I have split the single string and used cursor but now I want to avoid cursor. How I can achieve this.
declare #temp table(Chars int not null, RowID int not null)
--Split the rights string into 250 char rows with RowID as each position
;with cte as
(
select substring(#rights, 1, 1) as Chars,
stuff(#rights, 1, 1, '') as rights,
1 as RowID
union all
select substring(rights, 1, 1) as Chars,
stuff(rights, 1, 1, '') as rights,
RowID + 1 as RowID
from cte
where len(rights) > 0
)
--Get the values in a temporary table except 0
insert into #temp select Chars, RowID from cte option (MAXRECURSION 300);

What about this?
The idea is: Get a list of 250 running numbers and use SUBSTRING to read one character from each position:
DECLARE #tbl TABLE (ID INT IDENTITY,Rights VARCHAR(250));
INSERT INTO #tbl VALUES
('1011101110000101010111000...'), ('0100010111100010101...');
WITH Nr250 AS
(SELECT TOP 250 ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS Nr FROM master.dbo.spt_values)
SELECT t.ID
,t.Rights
,Nr AS Position
,SUBSTRING(t.Rights,Nr,1) AS PosDigit
--INTO #SomeTempTable
FROM Nr250
CROSS JOIN #tbl AS t
If you want to write this into a temp table, just remove the -- before INTO

Related

Generate Row Count, increasing in 10,000 per row

I'm needing to return a generated ID where for each row it increases by 10,000.
For example, the ExpectedResult column in the below, and if there are more rows, it would increase by 10,000 each time.
Create Table #temp
(
ID uniqueidentifier,
ExpectedResult int
)
insert into #temp
(
ID,
ExpectedResult
)
select
NEWID(),
10000
union
select
NEWID(),
20000
union
select
NEWID(),
30000
union
select
NEWID(),
40000
union
select
NEWID(),
50000
select * from #temp
order by ExpectedResult
drop table #temp
I've found the example below, but I'm not sure how to increase the count by 10,000 each time
ROW_NUMBER() OVER (ORDER BY (SELECT 100))
If you are using SQL Server 2012 or later (including SQL Server 2017), you could create a numbering sequence using CREATE SEQUENCE.
To create SEQUENCE with increment of 10000, add the clause INCREMENT BY.
For example:
CREATE SEQUENCE Test.CountBy1
START WITH 10000
INCREMENT BY 10000
For more information, please consult this SQL Server documentation on CREATE SEQUENCE:
https://learn.microsoft.com/sql/t-sql/statements/create-sequence-transact-sql?view=sql-server-2017
you can use cte to generate N number for guids. Below is a sample cte to generate 100 rows for your guid.
Drop table #temp
Create Table #temp
(
ID uniqueidentifier,
ExpectedResult int
);
with cte as(
select newid() as new_id, 10000 as ctr
union all
select new_id, ctr + 10000 from cte where ctr/10000 < 100
)
insert into #temp
select * from cte option (MaxRecursion 0 );
select * from #temp;
You need to divide ROW_NUMBER() by 10000 and then multiply by 10000
You can write something like this
select *, rowNum10K = 10000 * (1 + (row_number() over (order by object_id)) / 10000)
from #temp
As suggested by Nick in the original question comments, have done this:
ROW_NUMBER() OVER (ORDER BY (SELECT 100))*10000
You can define the column as an identity to do this for you:
Create Table temp (
ID uniqueidentifier,
ExpectedResult int identity (10000, 10000)
);
insert into temp (ID)
select v.id
from (values (NEWID()), (NEWID()), (NEWID()), (NEWID()), (NEWID())) v(id);
Here is a db<>fiddle.

Delete old entries from table in MSSQL

a table in my MSSQL db is getting over 500MB. I would like to delete the x last entries so the table size is only 100MB. This should be a task which runs once a week. How could I do this?
Example:
Table before deleting the old entries:
Table after deleting the old entries:
You can use DATALENGTH to get the size of the data in a particular column. With a window function, you can sum up a running total of DATALENGTH values. Then you can delete all records in a table that push you past a desired max table size. Here's an example:
-- Sample table with a VARBINARY(MAX) column
CREATE TABLE tmp (id INT IDENTITY(1,1) PRIMARY KEY, col VARBINARY(MAX))
-- Insert sample data - 20 bytes per row.
;WITH cte AS
(
SELECT 1 AS rn, HASHBYTES('sha1', 'somerandomtext') t
UNION all
SELECT rn + 1, HASHBYTES('sha1', 'somerandomtext')
FROM cte
WHERE rn< 5000
)
INSERT INTO tmp (col)
SELECT t FROM cte
OPTION (maxrecursion 0)
-- #total_bytes is the desired size of the table after the delete statement
DECLARE #total_bytes int = 200
-- Use the SUM window function to get a running total of the DATALENGTH
-- of the VARBINARY field, and delete when the running total exceeds
-- the desired table size.
-- You can order the window function however you want to delete rows
-- in the correct sequence.
DELETE t
FROM tmp t
INNER JOIN
(
SELECT id, SUM(DATALENGTH(col)) OVER (ORDER BY id) total_size
FROM tmp
)sq ON t.id = sq.id AND sq.total_size > #total_bytes
Now check what's left in tmp: 10 rows, and the total size of the "col" column matches the 200 byte size specified in the #total_bytes variable.
UPDATE: Here's an example using your sample data:
CREATE TABLE tmp (id INT PRIMARY KEY, contact VARCHAR(100), country VARCHAR(25))
GO
INSERT INTO tmp VALUES
(1, 'Maria Anders', 'Germany'),
(2, 'Francisco Chang', 'Mexico'),
(3, 'Roland Mendel', 'Austria'),
(4, 'Helen Bennett', 'UK'),
(5, 'Yoshi Tannamuri', 'Canada'),
(6, 'Giovanni Rovelli', 'Italy')
GO
-- #total_bytes is the desired size of the table after the delete statement
DECLARE #total_bytes INT = 40
-- Use the SUM window function to get a running total of the DATALENGTH
-- of the VARBINARY field, and delete when the running total exceeds
-- the desired table size.
-- You can order the window function however you want to delete rows
-- in the correct sequence.
DELETE t
FROM tmp t
INNER JOIN
(
SELECT id, SUM(DATALENGTH(contact)) OVER (ORDER BY id)
+ SUM(DATALENGTH(country)) OVER (ORDER BY id) total_size
FROM tmp
)sq ON t.id = sq.id AND sq.total_size > #total_bytes
SELECT * FROM tmp -- 2 rows left!
DELETE FROM TABLE_NAME WHERE date_column < '2018-01-01';
This will delete all data that entered before January 2018
if you want to delete last 7days data
delete from table_name WHERE date_column >= DATEADD(day,-7, GETDATE())

SQL Server stored procedure looping through a comma delimited cell

I am trying to figure out how to go about getting the values of a comma separated string that's present in one of my cells.
This is the query I current am trying to figure out in my stored procedure:
SELECT
uT.id,
uT.permissions
FROM
usersTbl AS uT
INNER JOIN
usersPermissions AS uP
/*Need to loop here I think?*/
WHERE
uT.active = 'true'
AND
uT.email = 'bbarker#thepriceisright.com'
The usersPermissions table looks like this:
And so a row in the usersTbl table looks like this for permissions:
1,3
I need to find a way to loop through that cell and get each number and place the name ****, in my returned results for the usersTbl.permissions.
So instead of returning this:
Name | id | permissions | age |
------------------------------------
Bbarker | 5987 | 1,3 | 87 |
It needs to returns this:
Name | id | permissions | age |
------------------------------------
Bbarker | 5987 | Read,Upload | 87 |
Really just replacing 1,3 with Read,Upload.
Any help would be great from a SQL GURU!
Reworked query
SELECT
*
FROM
usersTbl AS uT
INNER JOIN
usersPermissionsTbl AS uPT
ON
uPT.userId = uT.id
INNER JOIN
usersPermissions AS uP
ON
uPT.permissionId = uP.id
WHERE
uT.active='true'
AND
uT.email='bBarker#thepriceisright.com'
I agree with all of the comments... but strictly trying to do what you want, here's a way with a splitter function
declare #usersTbl table ([Name] varchar(64), id int, [permissions] varchar(64), age int)
insert into #usersTbl
values
('Bbarker',5987,'1,3',87)
declare #usersTblpermissions table (id int, [type] varchar(64))
insert into #usersTblpermissions
values
(1,'Read'),
(2,'Write'),
(3,'Upload'),
(4,'Admin')
;with cte as(
select
u.[Name]
,u.id as UID
,p.id
,p.type
,u.age
from #usersTbl u
cross apply dbo.DelimitedSplit8K([permissions],',') x
inner join #usersTblpermissions p on p.id = x.Item)
select distinct
[Name]
,UID
,age
,STUFF((
SELECT ',' + t2.type
FROM cte t2
WHERE t.UID = t2.UID
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
from cte t
Jeff Moden Splitter
CREATE FUNCTION [dbo].[DelimitedSplit8K] (#pString VARCHAR(8000), #pDelimiter CHAR(1))
--WARNING!!! DO NOT USE MAX DATA-TYPES HERE! IT WILL KILL PERFORMANCE!
RETURNS TABLE WITH SCHEMABINDING AS
RETURN
/* "Inline" CTE Driven "Tally Table" produces values from 1 up to 10,000...
enough to cover VARCHAR(8000)*/
WITH E1(N) AS (
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), --10E+1 or 10 rows
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 (--==== This provides the "base" CTE and limits the number of rows right up front
-- for both a performance gain and prevention of accidental "overruns"
SELECT TOP (ISNULL(DATALENGTH(#pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
SELECT 1 UNION ALL
SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(#pString,t.N,1) = #pDelimiter
),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
SELECT s.N1,
ISNULL(NULLIF(CHARINDEX(#pDelimiter,#pString,s.N1),0)-s.N1,8000)
FROM cteStart s
)
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
Item = SUBSTRING(#pString, l.N1, l.L1)
FROM cteLen l
;
GO
First, you should read Is storing a delimited list in a database column really that bad?, where you will see a lot of reasons why the answer to this question is Absolutely yes!
Second, you should add a table for user permissions since this is clearly a many to many relationship.
Your tables might look something like this (pseudo code):
usersTbl
(
Id int primary key
-- other user related columns
)
usersPermissionsTbl
(
UserId int, -- Foreign key to usersTbl
PermissionId int, -- Foreign key to permissionsTbl
Primary key (UserId, PermissionId)
)
permissionsTbl
(
Id int primary key,
Name varchar(20)
)
Once you have your tables correct, it's quite easy to get a list of comma separated values from the permissions table.
Adapting scsimon's sample data script to a correct many to many relationship:
declare #users table ([Name] varchar(64), id int, age int)
insert into #users values
('Bbarker',5987,87)
declare #permissions table (id int, [type] varchar(64))
insert into #permissions values
(1,'Read'),
(2,'Write'),
(3,'Upload'),
(4,'Admin')
declare #usersPermissions as table (userId int, permissionId int)
insert into #usersPermissions values (5987, 1), (5987, 3)
Now the query looks like this:
SELECT u.Name,
u.Id,
STUFF(
(
SELECT ','+ [type]
FROM #permissions p
INNER JOIN #usersPermissions up ON p.id = up.permissionId
WHERE up.userId = u.Id
FOR XML PATH('')
)
, 1, 1, '') As Permissions,
u.Age
FROM #Users As u
And the results:
Name Id Permissions Age
Bbarker 5987 Read,Upload 87
You can see a live demo on rextester.
I concur with much of the advice being presented to you in the other responses. The structure you're starting with is not going to be fun to maintain and work with. However, your situation may mean you are stuck with it so maybe some of the tools below will help you.
You can parse the delimiter with charindex() as others demonstrated here- MSSQL - How to split a string using a comma as a separator
... and even better here (several functions are provided) - Split function equivalent in T-SQL?
If you still want to do it with raw inline SQL and are committed to a loop, then pair the string manipulation with a CURSOR. Cursors have their own controversies BTW. The code below will work if your permission syntax remains consistent, which it probably doesn't.
They used charindex(',',columnName) and fed the location into the left() and right() functions along with some additional string evaluation to pull values out. You should be able to piece those together with a cursor
Your query might look like this...
--creating my temp structure
declare #userPermissions table (id int, [type] varchar(16))
insert into #userPermissions (id, [type]) values (1, 'Read')
insert into #userPermissions (id, [type]) values (2, 'Write')
insert into #userPermissions (id, [type]) values (3, 'Upload')
insert into #userPermissions (id, [type]) values (4, 'Admin')
declare #usersTbl table ([Name] varchar(16), id int, [permissions] varchar(8), age int)
insert into #usersTbl ([Name], id, [permissions], age) values ('Bbarker', 5987, '1,3', 87)
insert into #usersTbl ([Name], id, [permissions], age) values ('Mmouse', 5988, '2,4', 88)
--example query
select
ut.[Name]
, (select [type] from #userPermissions where [id] = left(ut.[permissions], charindex(',', ut.[permissions])-1) )
+ ','
+ (select [type] from #userPermissions where [id] = right(ut.[permissions], len(ut.[permissions])-charindex(',', ut.[permissions])) )
from #usersTbl ut

Add empty rows to results

I want to add empty rows to results fetched from a select statement. For example, if the select query fetch 4 rows then 2 empty rows needs to be fetched. Objective should be the number of rows fetched should be 6 every time. The number of rows fetched will be 6 maximum if there are 6 rows with data.
Any idea?
In SQL-SERVER You can create temp table to update It with empty rows and you can use WHILE to insert desired number of rows with empty values. Something like:
-- Create temp table to update data with empty rows
CREATE TABLE #HoldEmptyRows
(
Id NVARCHAR(20),
CustomerName NVARCHAR(20),
CustomerEmail NVARCHAR(20)
)
-- Insert data from SourceTable to temp
INSERT INTO #HoldEmptyRows
SELECT * FROM SourceTable
-- Do while count from temp table < of desired number insert empty rows
WHILE ((SELECT COUNT(*) cnt FROM #HoldEmptyRows) < 6)
BEGIN
INSERT INTO #HoldEmptyRows VALUES ('', '', '')
END
SELECT * FROM #HoldEmptyRows
DEMO AT SQL FIDDLE
Try the below logic:
with cte as
(
select 0 as col1
union all
select col1+1 from cte where cte.col1<10
)
select * into #temp1 from cte
create table #test
(rownum int,col1 varchar(100))
declare #i int=1
while (#i<=6)
begin
insert into #test
select * from
(select row_Number() over (order by (Select 0))rownum, * from #temp1)x
where rownum=#i
Set #i=#i+1
end
select case when rownum>4 then '' else col1 end as col1 from #test

Tally Table in SQL

I want to create a bunch of data with Tally table in SQL (sql2008) and definitely need help.
First of all, I have this table which contains 2 columns.
{
AcctNum (nchar(30), null),
DataInfo (nchar(745), null)
}
While I don't care the data in the DataInfo column, I do want to add about 10k of row into the table with unique AcctNum on each row.
The problem though is I need to keep the length of the data in both column. For example, AcctNum column looks like "400000000000001 ". how do I increment the number while keep the "blank space"?
Not sure if I make much sense here, but please let me know and I will try to explain more, thanks!
Using a recursive common table expression :
-- set up a table variable for demo purpose
declare #t table (AcctNum nchar(30) null, DataInfo nchar(745) null);
-- insert the starting value
insert #t values ('400000000000001', null);
-- run the cte to generate the sequence
with cte (acctnum, num) as (
select acctnum, cast(acctnum as bigint) + 1 num -- starting value
from #t
union all
select acctnum, num+1 from cte
where num < cast(acctnum as bigint) + 10000 -- stopping value
)
-- insert data sequence into the table
insert #t (AcctNum, DataInfo)
select num, null from cte
option (maxrecursion 10000);
select * from #t;
The table variable #t will now contain acctnum 400000000000001 -> 400000000010001 as a contiguous sequence.