List Lines per Quantity - sql

I'm probably over thinking this. I have a simple table with Name and Ticket Quantity columns. I want to output a row by row list of the names for each quantity purchased. See example below.
Table:
Name Quantity
-----------------------
Bob 1
Joe 2
Sally 1
Output:
Bob
Joe
Joe
Sally
How could I achieve this in TSQL?

SETUP:
DECLARE #table TABLE (
NAME VARCHAR(10),
Quantity INT
)
INSERT INTO #table
SELECT 'Bob', 1 UNION ALL
SELECT 'Joe', 2 UNION ALL
SELECT 'Sally', 1
Recursive CTE
;WITH Members (
NAME,
Quantity
)
AS (
-- Base case
SELECT NAME,
Quantity
FROM #table
UNION ALL
-- Recursive
SELECT NAME,
Members.Quantity - 1
FROM Members
WHERE Members.Quantity > 1
)
SELECT NAME
FROM Members
OPTION (MAXRECURSION 0)
ORDER BY 1
Result:
Bob
Joe
Joe
Sally
Alternatively you could (per #Martin Smith's suggestion):
DECLARE #numbers TABLE (number INT)
INSERT INTO #numbers (number)
VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9), (10)
Finally:
SELECT NAME
FROM #table t
INNER JOIN #numbers n ON n.number <= t.Quantity
ORDER BY 1
Result:
Bob
Joe
Joe
Sally
And if you really like recursive CTE's (because they smell good), you could build your numbers table with a recursive CTE. You should be using physical tables and not variable tables as you see here - so that you don't have to build them every time.
;WITH Numbers (Value)
AS (
-- Base case
SELECT 32767 Value
UNION ALL
-- Recursive
SELECT Numbers.Value - 1
FROM Numbers
WHERE Numbers.Value > 1
)
INSERT INTO #numbers (number)
SELECT Value
FROM Numbers
OPTION (MAXRECURSION 32767)

Related

Increment in the value of each row based on iterations in SQL

How can I have an increment on a column in each row based on the values of other column till specific iterations?
For example:
There are 2 columns Iterations and IncrementRatio. The value of Iterations is 5 and IncrementRatio is 5000. The query is supposed to generate 10 records only. Once the iteration number is reached the remaining rows should keep the last value. So based on this scenario I should be able to get something like this.
MyCount
---------
5000
10000
15000
20000
25000
25000
25000
25000
25000
25000
I assumed you always wanted fixed 10 rows result. This is achieve with CROSS JOIN to a derived table with 10 rows. In the solution, i am using Table Value Constructor to generate 10 rows. You can replace it with a tally table if you have one.
-- create the sample table for demonstration of the query
declare #table table
(
Id int identity,
Iterations int,
IncrementRatio int
)
-- insert original sample data plus one extra
insert into #table values (5, 5000), (3, 1000)
-- the query
select *,
MyCount = case when n < Iterations
then n * IncrementRatio
else Iterations * IncrementRatio
end
from #table
cross join
(
values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10)
) num (n)
I would use a recursive cte:
with cte as (
select iterations, incrementratio, 1 as i, incrementratio as cnt
from t
union all
select iterations, incrementratio, i + 1,
(case when i < iterations then t.incrementratio + cnt
else cnt
end)
from t
where i < 10
)
select *
from cte;

Add extra rows in result set of query with dummy data in sql server

I have this table let's say Students. If there are 2 students in table and result set gives 2 rows than I want to add 5 more rows with dummy data in it no matter what it says i.e 'Dummy Record' or something.
SELECT FirstName, (SELECT COUNT(*) FROM Student) Total
FROM Student
If the output is like this from above query.
FirstName Total
Isaac Frempong 2
Erick Ortiz 2
I want the output to be like this
FirstName Total
Isaac Frempong 2
Erick Ortiz 2
Dummy Data 2
Dummy Data 2
Dummy Data 2
Dummy Data 2
Dummy Data 2
I hope it's achievable, I'm unable to figure how to apply CASE or IF statements here. Maybe somebody could help.
One method is to use a VALUES() statement to construct the rows:
SELECT FirstName, COUNT(*) OVER () as Total
FROM Student
UNION ALL
SELECT 'Dummy', (SELECT COUNT(*) FROM Student)
FROM (VALUES (1), (2), (3), (4), (5)) v(n);
EDIT:
If you always want 7 rows, use arithmetic:
SELECT FirstName, COUNT(*) OVER () as Total
FROM Student
UNION ALL
SELECT 'Dummy', s.total
FROM (VALUES (1), (2), (3), (4), (5), (6), (7)) v(n) CROSS JOIN
(SELECT COUNT(*) as total FROM Student) s
WHERE v.n <= 7 - s.total;
s
Here is a code working on a table varaibale I called #students
Please replace it by your own table
I have filled this table with some Data, and tested the different cases (records number less then 7 or more than 7, and worked as you wish !)
1) First create a variable table called #mytable which will be filled by n records
n will be 7 if the number of records in your students table is equal or less than 7
n will be the number of records in your student table if it is more than 7
2) Then make a right outer join between your student table to which you add the record number using CTE ,row_number() function.
declare #students as table(id int identity(1,1),firstname nvarchar(50))
insert into #students(firstname) values
('Ali ben Hassine'),
('Mohamed el Aabed'),
('Ali ben Hassine'),
('Mohamed el Aabed'),
('Mohamed el Aabed'),
('Tahar Harbi'),
('Hassine Ayari'),
('Ihsen Trabelsi'),
('Marwa Mostari'),
('Mourad Zmerli'),
('Hafedh Gabsi'),
('Miloud Filali');
declare #mytab as table(n int)
declare #n as int
select #n=count(distinct(firstname)) from #students
if #n<7 set #n=7
declare #i as int
set #i=1
while #i <#n+1
begin
insert into #mytab values(#i)
set #i=#i+1
end;
with cte as
(select row_number() over(partition by 1 order by firstname) r#,firstname,count(1) Total from #students group by firstname)
select isnull(cte.firstname,'Dummy') firstanme,isnull(cte.total,2) Total from cte
right outer join #mytab t2 on cte.r#=t2.n
[![enter image description here][1]][1]

Find missing numbers in a sequence in MS SQL

Say i have a table with an integer column Id. I need to find the missing numbers in a sequence with a maximum returned amount.
If the table is empty and i'm asking for 10, it should return the numbers 1-10.
If the table has 1-5 and i'm asking for 10, it should return the numbers 6,7,8,9,10,11,12,13,14,15.
If the table has 1,2,4,6,9 and im asking for 10, it should return the numbers 3,5,7,8,10,11,12,13,14,15
How can i achive this in one single query using MS SQL?
Thanks in advance!
Try this:
If you need to get more numbers, just increase the WHERE Number<=100.
DECLARE #Tab1 TABLE (ID INT)
INSERT INTO #Tab1 VALUES(1)
INSERT INTO #Tab1 VALUES(3)
INSERT INTO #Tab1 VALUES(5)
INSERT INTO #Tab1 VALUES(7)
INSERT INTO #Tab1 VALUES(9)
;WITH CTE AS
(
SELECT 1 AS Number
UNION ALL
SELECT Number + 1 FROM CTE
WHERE Number<=100
)
SELECT TOP 5 *
FROM CTE
WHERE Number NOT IN(SELECT ID FROM #Tab1)
ORDER BY Number
OPTION (maxrecursion 0);
Existing values:
Number
1
3
5
7
9
OutPut:
Number
2
4
6
8
10
Hope this helps you.
This should work
There are also a system table with numbers
declare #T table (i int primary key);
insert into #T values (1), (2), (4), (6), (9);
declare #count int = 10;
declare #size int = (select count(*) from #T);
with cte as
( select 1 as num
union all
select num + 1
from cte
where num + 1 <= (#count + #size)
)
select top (#count) cte.num
from cte
left join #T t
on t.i = cte.num
where t.i is null
order by cte.num
option ( MaxRecursion 0 );

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

Turn value into singular row

Current
Name Quantity
---------------
Stella 2
Jennifer 2
Greg 3
Requested result
Name Quantity
---------------
Stella 1
Stella 1
Jennifer 1
Jennifer 1
Greg 1
Greg 1
Greg 1
How should I do it?
declare #T table
(
Name varchar(50),
Sales int
)
insert into #T values
('Stella', '2'),
('Jennifer', '2'),
('Greg', '3')
If the maximum value in the quantity column is known to be less than 32,767, you can use Recursion to generate numbers and join the Numbers to achieve your result.
/*******************************************
Max Recursion Count in SQL Server is 32767
Limitation of 32767 Numbers!
******************************************/
;WITH Numbers (Number) AS
(
SELECT 1
UNION ALL
SELECT 1 + Number FROM Numbers WHERE Number < 100
)
SELECT m.Name,
Quantity = 1
FROM MyTable m
JOIN #numbers n ON m.Quantity <= n.Number
OPTION (MAXRECURSION 32767);
Using recursion and borrowing Michael Fredrickson's setup code:
declare #T table (
Name varchar(50),
Sales int
)
insert into #T values ('Stella', '2')
insert into #T values ('Jennifer', '2')
insert into #T values ('Greg', '3')
-- Recursive verion
;with People (Name, Sales) as
(
select Name, Sales
from #T
union all
select Name, Sales - 1
from People
where Sales - 1 > 0
)
select Name, 1 as Quantity
from People
option (maxrecursion 0) -- Recurse without limit
This seems to run faster on my box (5x faster than Michael Fredrickson's according to query plan, but with many more logical reads), not that it matters much.
You'll probably want to have a pre-populated numbers table to do this:
declare #T table (
Name varchar(50),
Sales int
)
declare #numbers table (
Number int
)
insert into #numbers values (1)
insert into #numbers values (2)
insert into #numbers values (3)
insert into #numbers values (4)
-- Etc... up to however many numbers is the max possible value for sales...
insert into #T values ('Stella', '2')
insert into #T values ('Jennifer', '2')
insert into #T values ('Greg', '3')
SELECT
t.Name,
1 AS Sales
FROM
#T t JOIN
#numbers n ON
t.Sales >= n.Number
ORDER BY t.Name
That's how you could do it, but I'm not sure on why you would want to do it.