Insert n rows stored procedure - sql

Trying to insert multiple rows based on a passed parameter.
Tried this and it only repeated once:
ALTER PROCEDURE [dbo].[ActivateCertificates]
#Count int,
#CertificateNumber int,
#Buyer VarChar(50)
AS
Declare #x int
Declare #InitialCharacter Char(1)
SET #InitialCharacter='C'
Declare #Last3 Char(3)
SET #Last3='867'
while #x <= #Count
begin
/* CREATE THE CERTIFICATE NUMBER */
set #CertificateNumber = #CertificateNumber +1
/* insert into certificates cert number and who sold to. */
INSERT into Certificates (CertificateNumber,Buyer)
VALUES (#InitialCharacter + ltrim(rtrim(cast(#CertificateNumber as char))) + #Last3, #Buyer)
end
set #x =#x + 1
GO

Your variable is incremented outside the BEGIN/END block. Also I would recommend you initially set your variable to 0 to avoid any potential garbage data stored in that memory location.

To achieve your goal using a set-based query instead of a loop (BTW there are lots of such examples here on StackOverflow) you'll have to have a tally (numbers) table or create it on a fly with a subquery or recursive CTE.
CREATE TABLE tally (id INT NOT NULL PRIMARY KEY);
To populate it up to 100000 (Celko-style)
INSERT INTO tally (id)
SELECT a.N + b.N * 10 + c.N * 100 + d.N * 1000 + e.N * 10000 + 1 as N
FROM (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) a
, (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) b
, (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) c
, (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) d
, (select 0 as N union all select 1 union all select 2 union all select 3 union all select 4 union all select 5 union all select 6 union all select 7 union all select 8 union all select 9) e
ORDER BY N;
Now your stored procedure boils down to one statement
CREATE PROCEDURE ActivateCertificates
#Count INT,
#CertificateNumber INT,
#Buyer VARCHAR(50)
AS
INSERT INTO Certificates (CertificateNumber, Buyer)
SELECT 'C' + CAST(q.number + t.id AS VARCHAR(12)) + '867', q.buyer
FROM
(
SELECT #CertificateNumber number, #Buyer buyer
) q, tally t
WHERE t.id <= #Count;
Here is SQLFiddle demo
Now if you generate relatively small amounts of certificates (< 32768) then you can use recursive CTE to build a sequence of numbers (and don't need a persisted tally table)
CREATE PROCEDURE ActivateCertificates
#Count INT,
#CertificateNumber INT,
#Buyer VARCHAR(50)
AS
WITH tally AS (
SELECT 1 id
UNION ALL
SELECT id + 1 FROM tally WHERE id < #Count
)
INSERT INTO Certificates (CertificateNumber, Buyer)
SELECT 'C' + CAST(q.number + t.id AS VARCHAR(12)) + '867', q.buyer
FROM
(
SELECT #CertificateNumber number, #Buyer buyer
) q, tally t OPTION (MAXRECURSION 32767);
Here is SQLFiddle demo for that case

Related

How to insert missing numbers that depends on multiple columns sql

so I have this kind of table:
What you can see is that I have a missing month number which is 1.
What I want is to insert a value that is like this to my table:
Month Year Col1 Forecast Customer
1 2015 HD/FB/BK/ 0 AFC
since that was a missing month, so I want the forecast to be 0 value.
And my parameters for such insert would be by year,col1 and customer.
First, generate rows of all month-year combination based on your parameters. Then use NOT EXISTS to insert missing rows:
DECLARE #yr INT,
#col1 VARCHAR(50),
#customer VARCHAR(50);
WITH Cte(mnt, yr, col1, customer) AS(
SELECT *, #yr, #col1, #customer
FROM(VALUES
(1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11), (12)
)t(N)
)
INSERT INTO tbl(Month, Year, Col1, Forecast, Customer)
SELECT
mnt,
yr,
col1,
0,
customer
FROM Cte c
WHERE
NOT EXISTS(
SELECT 1
FROM tbl
WHERE
Col1 = c.col1
AND Customer = c.customer
AND Month = c.mnt
AND Year = c.yr
)
Approach here is to generate combinations of month and col1 and match with the current table and generate the insert for missing rows. You may extend this with other required combinations of customer and year. This works well if there is no single entry for a particular combination.
Step 1: Create combinations table
SELECT
*
FROM
(SELECT 1 month UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12) months
JOIN
(SELECT 'HD/FB/BK' colname UNION ALL SELECT 'HD/FB/BL') col1
Step 2: Find missing items
select expected.* from data_table dt right join (SELECT
*
FROM
(SELECT 1 month UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12) months
JOIN
(SELECT 'HD/FB/BK' colname UNION ALL SELECT 'HD/FB/BL') col1) expected on (dt.month = expected.month and dt.Col1 = expected.colname) where dt.col1 is null
Step 3: Insert missing values (All combined)
insert into data_table
select datainsert.month, 2015, datainsert.colname, 0.0, 'AFC' FROM
(select expected.* from data_table dt right join (SELECT
*
FROM
(SELECT 1 month UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12) months
JOIN
(SELECT 'HD/FB/BK' colname UNION ALL SELECT 'HD/FB/BL') col1) expected on (dt.month = expected.month and dt.Col1 = expected.colname) where dt.col1 is null) datainsert;

SQL - Lookup multiple values from same field

Issue and desired result explained in SQL code below. I had a partially working solution using a cursor and while loop sub-statement but was still struggling with the fact that there can be any number of userIds in the same field (0-10 for instance) and some of the userids may not even have a valid match in the lookup table.
Example data source table. We need to find the user names based on the user ids
drop table #sourcetable
Create table #sourcetable (rowid int
,userId varchar(50))
Insert #sourcetable
Values ('1','123456789')
Insert #sourcetable
Values ('2','123456789'+','+'456821495')
Insert #sourcetable
Values ('3','123456789'+','+'456821495'+','+'589642304')
Example lookup table. We have a table with all users listed by user id
drop table #lookuptable
Create table #lookuptable (userId varchar(50)
,Username varchar(100))
Insert #lookuptable
Values ('123456789','User A')
Insert #lookuptable
Values ('456821495','User B')
Insert #lookuptable
Values ('589642304','User C')
This is the expected result. The issue is that there can be multiple (any number) userids in the 1 userid field in the source table. The only constant factors are that valid userids will always be 9 digits and each will be seperate by ',' if there are multiple IDs in the field. (SQL v2008 server)
drop table #Resulttable
Create table #Resulttable (rowid int
,userId varchar(50)
,Username varchar(100))
Insert #Resulttable
Values ('1','123456789','User A')
Insert #Resulttable
Values ('2','123456789'+','+'456821495','User A,User B')
Insert #Resulttable
Values ('3','123456789'+','+'456821495'+','+'589642304','User A,User B,User C')
select *
from #lookuptable
select *
from #sourcetable
select *
from #Resulttable
I recommend to change the way your data is stored. SQL Server is not optimized to have multiple values in a single field. If you can't change the way the data is stored, here is a way you can get your desired results(I cleaned up your code a bit).
IF OBJECT_ID('tempdb..#sourcetable') IS NOT NULL
DROP TABLE #sourceTable
CREATE TABLE #sourcetable (
rowid INT,
userId VARCHAR(50)
);
INSERT INTO #sourcetable
VALUES ('1','123456789'),
('2','123456789'+','+'456821495'),
('3','123456789'+','+'456821495'+','+'589642304');
IF OBJECT_ID('tempdb..#lookuptable') IS NOT NULL
DROP TABLE #lookupTable;
CREATE TABLE #lookuptable (
userId varchar(50),
Username varchar(100)
);
INSERT INTO #lookuptable
VALUES ('123456789','User A'),
('456821495','User B'),
('589642304','User C');
IF OBJECT_ID('tempdb..#ResultTable') IS NOT NULL
DROP TABLE #ResultTable
CREATE TABLE #Resulttable ( rowid INT,
userId VARCHAR(50),
Username VARCHAR(100)
);
INSERT INTO #Resulttable
VALUES ('1','123456789','User A'),
('2','123456789'+','+'456821495','User A,User B'),
('3','123456789'+','+'456821495'+','+'589642304','User A,User B,User C');
WITH CTE_Results
AS
(
select B.rowID,
B.userId,
A.Username
from #lookuptable A
LEFT JOIN #sourcetable B
ON CAST(B.userId AS VARCHAR(100)) LIKE '%' + CAST(A.userId AS VARCHAR(10)) + '%'
)
SELECT rowid,
userId,
STUFF((
SELECT ',' + Username
FROM CTE_Results A
WHERE (A.userId = B.UserID)
ORDER BY Username
FOR XML PATH(''),TYPE).value('(./text())[1]','VARCHAR(MAX)')
,1,1,'') AS Usernames
FROM CTE_Results B
GROUP BY rowid, userId;
Results:
rowid userId Usernames
----------- ----------------------------- -----------------------
1 123456789 User A
2 123456789,456821495 User A,User B
3 123456789,456821495,589642304 User A,User B,User C
I only can agree with BDL: You should not store your data like that.
But sometimes you have no choice so here a working solution.
What you need to do is to split up your userIds first, then join, and then concatenate your Usernames again. This would then look like:
insert into #Resulttable (rowid, userId, Username)
select rowid, userId
, stuff(
(select ',' + l.Username
from #sourcetable as s
cross apply dbo.udf_SplitWordList(s.userId,',') as split
join #lookuptable as l
on l.userId = split.Word
where s.rowid = s_out.rowid
for XML PATH(''), type).value('(./text())[1]','nvarchar(max)')
, 1, 1, '') Username
from #sourcetable as s_out
You can replace my splitting function [dbo].[udf_SplitWordList] with your preferred one.
If you haven't one yet, this is the one I use
-- =============================================
-- Description: This function can split up a sting by a given delimiter
-- Limitations: Currently the maximum input string length is 983,040. To process longer strings, the function has to be adjusted.
-- USAGE:
/*
SELECT * FROM dbo.udf_SplitWordList('Hello,World',',') -- Separater = ,
SELECT * FROM dbo.udf_SplitWordList('Hello|World','|') -- Separater = ,
SELECT * FROM dbo.udf_SplitWordList('HelloWorld',CHAR(127)) -- Separater = DEL
SELECT * FROM dbo.udf_SplitWordList('HelloWorld',CHAR(27)) -- Separater = ESC
*/
-- =============================================
CREATE FUNCTION [dbo].[udf_SplitWordList]
(
#list NVARCHAR(MAX)
, #delimiter NVARCHAR(10)
)
RETURNS #t TABLE
(
Word NVARCHAR(MAX) NOT NULL,
Position INT IDENTITY(1,1) NOT NULL
)
AS
BEGIN
DECLARE #list_len BIGINT, #del_len INT
SET #list_len = LEN(#list)
SET #del_len = LEN(REPLACE(#delimiter,' ','_'))
IF #list_len < 16
INSERT #t
SELECT SUBSTRING(#delimiter + #List + #delimiter, w.i + #del_len, CHARINDEX(#delimiter, #delimiter + #List + #delimiter, w.i + #del_len) - w.i - #del_len) value
FROM (
SELECT v0.n i
FROM (SELECT 0 n UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12 UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL SELECT 15) v0
) w
WHERE w.i = CHARINDEX(#delimiter, #delimiter + #List + #delimiter, w.i) AND w.i < #list_len + #del_len
ORDER BY i
ELSE IF #list_len < 256
INSERT #t
SELECT SUBSTRING(#delimiter + #List + #delimiter, w.i + #del_len, CHARINDEX(#delimiter, #delimiter + #List + #delimiter, w.i + #del_len) - w.i - #del_len) value
FROM (
SELECT v0.n + v1.n i
FROM (SELECT 0 n UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12 UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL SELECT 15) v0
, (SELECT 0 n UNION ALL SELECT 16 UNION ALL SELECT 32 UNION ALL SELECT 48 UNION ALL SELECT 64 UNION ALL SELECT 80 UNION ALL SELECT 96 UNION ALL SELECT 112 UNION SELECT 128 UNION ALL SELECT 144 UNION ALL SELECT 160 UNION ALL SELECT 176 UNION ALL SELECT 192 UNION ALL SELECT 208 UNION ALL SELECT 224 UNION ALL SELECT 240) v1
) w
WHERE w.i = CHARINDEX(#delimiter, #delimiter + #List + #delimiter, w.i) AND w.i < #list_len + #del_len
ORDER BY i
ELSE IF #list_len < 4096
INSERT #t
SELECT SUBSTRING(#delimiter + #List + #delimiter, w.i + #del_len, CHARINDEX(#delimiter, #delimiter + #List + #delimiter, w.i + #del_len) - w.i - #del_len) value
FROM (
SELECT v0.n + v1.n + v2.n i
FROM (SELECT 0 n UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12 UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL SELECT 15) v0
, (SELECT 0 n UNION ALL SELECT 16 UNION ALL SELECT 32 UNION ALL SELECT 48 UNION ALL SELECT 64 UNION ALL SELECT 80 UNION ALL SELECT 96 UNION ALL SELECT 112 UNION SELECT 128 UNION ALL SELECT 144 UNION ALL SELECT 160 UNION ALL SELECT 176 UNION ALL SELECT 192 UNION ALL SELECT 208 UNION ALL SELECT 224 UNION ALL SELECT 240) v1
, (SELECT 0 n UNION ALL SELECT 256 UNION ALL SELECT 512 UNION ALL SELECT 768 UNION ALL SELECT 1024 UNION ALL SELECT 1280 UNION ALL SELECT 1536 UNION ALL SELECT 1792 UNION SELECT 2048 UNION ALL SELECT 2304 UNION ALL SELECT 2560 UNION ALL SELECT 2816 UNION ALL SELECT 3072 UNION ALL SELECT 3328 UNION ALL SELECT 3584 UNION ALL SELECT 3840) v2
) w
WHERE w.i = CHARINDEX(#delimiter, #delimiter + #List + #delimiter, w.i) AND w.i < #list_len + #del_len
ORDER BY i
ELSE IF #list_len < 65536
INSERT #t
SELECT SUBSTRING(#delimiter + #List + #delimiter, w.i + #del_len, CHARINDEX(#delimiter, #delimiter + #List + #delimiter, w.i + #del_len) - w.i - #del_len) value
FROM (
SELECT v0.n + v1.n + v2.n + v3.n i
FROM (SELECT 0 n UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12 UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL SELECT 15) v0
, (SELECT 0 n UNION ALL SELECT 16 UNION ALL SELECT 32 UNION ALL SELECT 48 UNION ALL SELECT 64 UNION ALL SELECT 80 UNION ALL SELECT 96 UNION ALL SELECT 112 UNION SELECT 128 UNION ALL SELECT 144 UNION ALL SELECT 160 UNION ALL SELECT 176 UNION ALL SELECT 192 UNION ALL SELECT 208 UNION ALL SELECT 224 UNION ALL SELECT 240) v1
, (SELECT 0 n UNION ALL SELECT 256 UNION ALL SELECT 512 UNION ALL SELECT 768 UNION ALL SELECT 1024 UNION ALL SELECT 1280 UNION ALL SELECT 1536 UNION ALL SELECT 1792 UNION SELECT 2048 UNION ALL SELECT 2304 UNION ALL SELECT 2560 UNION ALL SELECT 2816 UNION ALL SELECT 3072 UNION ALL SELECT 3328 UNION ALL SELECT 3584 UNION ALL SELECT 3840) v2
, (SELECT 0 n UNION ALL SELECT 4096 UNION ALL SELECT 8192 UNION ALL SELECT 12288 UNION ALL SELECT 16384 UNION ALL SELECT 20480 UNION ALL SELECT 24576 UNION ALL SELECT 28672 UNION ALL SELECT 32768 UNION ALL SELECT 36864 UNION ALL SELECT 40960 UNION ALL SELECT 45056 UNION ALL SELECT 49152 UNION ALL SELECT 53248 UNION ALL SELECT 57344 UNION ALL SELECT 61440) v3
) w
WHERE w.i = CHARINDEX(#delimiter, #delimiter + #List + #delimiter, w.i) AND w.i < #list_len + #del_len
ORDER BY i
ELSE
INSERT #t
SELECT SUBSTRING(#delimiter + #List + #delimiter, w.i + #del_len, CHARINDEX(#delimiter, #delimiter + #List + #delimiter, w.i + #del_len) - w.i - #del_len) value
FROM (
SELECT v0.n + v1.n + v2.n + v3.n + v4.n i
FROM (SELECT 0 n UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12 UNION ALL SELECT 13 UNION ALL SELECT 14 UNION ALL SELECT 15) v0
, (SELECT 0 n UNION ALL SELECT 16 UNION ALL SELECT 32 UNION ALL SELECT 48 UNION ALL SELECT 64 UNION ALL SELECT 80 UNION ALL SELECT 96 UNION ALL SELECT 112 UNION SELECT 128 UNION ALL SELECT 144 UNION ALL SELECT 160 UNION ALL SELECT 176 UNION ALL SELECT 192 UNION ALL SELECT 208 UNION ALL SELECT 224 UNION ALL SELECT 240) v1
, (SELECT 0 n UNION ALL SELECT 256 UNION ALL SELECT 512 UNION ALL SELECT 768 UNION ALL SELECT 1024 UNION ALL SELECT 1280 UNION ALL SELECT 1536 UNION ALL SELECT 1792 UNION SELECT 2048 UNION ALL SELECT 2304 UNION ALL SELECT 2560 UNION ALL SELECT 2816 UNION ALL SELECT 3072 UNION ALL SELECT 3328 UNION ALL SELECT 3584 UNION ALL SELECT 3840) v2
, (SELECT 0 n UNION ALL SELECT 4096 UNION ALL SELECT 8192 UNION ALL SELECT 12288 UNION ALL SELECT 16384 UNION ALL SELECT 20480 UNION ALL SELECT 24576 UNION ALL SELECT 28672 UNION ALL SELECT 32768 UNION ALL SELECT 36864 UNION ALL SELECT 40960 UNION ALL SELECT 45056 UNION ALL SELECT 49152 UNION ALL SELECT 53248 UNION ALL SELECT 57344 UNION ALL SELECT 61440) v3
, (SELECT 0 n UNION ALL SELECT 65536 UNION ALL SELECT 131072 UNION ALL SELECT 196608 UNION ALL SELECT 262144 UNION ALL SELECT 327680 UNION ALL SELECT 393216 UNION ALL SELECT 458752 UNION ALL SELECT 524288 UNION ALL SELECT 589824 UNION ALL SELECT 655360 UNION ALL SELECT 720896 UNION ALL SELECT 786432 UNION ALL SELECT 851968 UNION ALL SELECT 917504 UNION ALL SELECT 983040) v4
) w
WHERE w.i = CHARINDEX(#delimiter, #delimiter + #List + #delimiter, w.i) AND w.i < #list_len + #del_len
ORDER BY i
RETURN
END

How can I find unoccupied id numbers in a table?

In my table I want to see a list of unoccupied id numbers in a certain range.
For example there are 10 records in my table with id's: "2,3,4,5,10,12,16,18,21,22" and say that I want to see available ones between 1 and 25. So I want to see a list like:
1,6,7,89,11,13,14,15,17,19,20,23,24,25
How should I write my sql query?
Select the numbers form 1 to 25 and show only those that are not in your table
select n from
( select rownum n from dual connect by level <= 25)
where n not in (select id from table);
Let's say you a #numbers table with three numbers -
CREATE TABLE #numbers (num INT)
INSERT INTO #numbers (num)
SELECT 1
UNION
SELECT 3
UNION
SELECT 6
Now, you can use CTE to generate numbers recursively from 1-25 and deselect those which are in your #numbers table in the WHERE clause -
;WITH n(n) AS
(
SELECT 1
UNION ALL
SELECT n+1 FROM n WHERE n < 25
)
SELECT n FROM n
WHERE n NOT IN (select num from #numbers)
ORDER BY n
OPTION (MAXRECURSION 25);
You can try using the "NOT IN" clause:
select
u1.user_id + 1 as start
from users as u1
left outer join users as u2 on u1.user_id + 1 = u2.id
where
u2.id is null
see also SQL query to find Missing sequence numbers
You need LISTAGG to get the output in a single row.
SQL> WITH DATA1 AS(
2 SELECT LEVEL rn FROM dual CONNECT BY LEVEL <=25
3 ),
4 data2 AS(
5 SELECT 2 num FROM dual UNION ALL
6 SELECT 3 FROM dual UNION ALL
7 SELECT 4 from dual union all
8 SELECT 5 FROM dual UNION ALL
9 SELECT 10 FROM dual UNION ALL
10 SELECT 12 from dual union all
11 SELECT 16 from dual union all
12 SELECT 18 FROM dual UNION ALL
13 SELECT 21 FROM dual UNION ALL
14 SELECT 22 FROM dual)
15 SELECT listagg(rn, ',')
16 WITHIN GROUP (ORDER BY rn) num_list FROM data1
17 WHERE rn NOT IN(SELECT num FROM data2)
18 /
NUM_LIST
----------------------------------------------------
1,6,7,8,9,11,13,14,15,17,19,20,23,24,25
SQL>

Is it possible to write a sql query that is grouped based on a running total of a column?

It would be easier to explain with an example. Suppose I wanted to get at most 5 items per group.
My input would be a table looking like this:
Item Count
A 2
A 3
A 3
B 4
B 4
B 5
C 1
And my desired output would look like this:
Item Count
A 5
A>5 3
B 4
B>5 9
C 1
An alternative output that I could also work with would be
Item Count RunningTotal
A 2 2
A 3 5
A 3 8
B 4 4
B 4 8
B 5 13
C 1 1
I can use ROW_NUMBER() to get the top X records in each group, however my requirement is to get the top X items for each group, not X records. My mind is drawing a blank as to how to do this.
declare #yourTable table (item char(1), [count] int)
insert into #yourTable
select 'A', 2 union all
select 'A', 3 union all
select 'A', 3 union all
select 'B', 4 union all
select 'B', 4 union all
select 'B', 5 union all
select 'C', 1
;with cte(item, count, row) as (
select *, row_number() over ( partition by item order by item, [count])
from #yourTable
)
select t1.Item, t1.Count, sum(t2.count) as RunningTotal from cte t1
join cte t2 on t1.item = t2.item and t2.row <= t1.row
group by t1.item, t1.count, t1.row
Result:
Item Count RunningTotal
---- ----------- ------------
A 2 2
A 3 5
A 3 8
B 4 4
B 4 8
B 5 13
C 1 1
Considering the clarifications from your comment, you should be able to produce the second kid of output from your post by running this query:
select t.Item
, t.Count
, (select sum(tt.count)
from mytable tt
where t.item=tt.item and (tt.creating_user_priority < t.creating_user_priority or
( tt.creating_user_priority = t.creating_user_priority and tt.created_date < t.createdDate))
) as RunningTotal
from mytable t
declare #yourTable table (item char(1), [count] int)
insert into #yourTable
select 'A', 2 union all
select 'A', 3 union all
select 'A', 3 union all
select 'B', 4 union all
select 'B', 4 union all
select 'B', 5 union all
select 'C', 1
;with cte(item, count, row) as (
select *, row_number() over ( partition by item order by item, [count])
from #yourTable
)
select t1.row, t1.Item, t1.Count, sum(t2.count) as RunningTotal
into #RunTotal
from cte t1
join cte t2 on t1.item = t2.item and t2.row <= t1.row
group by t1.item, t1.count, t1.row
alter table #RunTotal
add GrandTotal int
update rt
set GrandTotal = gt.Total
from #RunTotal rt
left join (
select Item, sum(Count) Total
from #RunTotal rt
group by Item) gt
on rt.Item = gt.Item
select Item, max(RunningTotal)
from #RunTotal
where RunningTotal <= 5
group by Item
union
select a.Item + '>5', total - five
from (
select Item, max(GrandTotal) total
from #RunTotal
where GrandTotal > 5
group by Item
) a
left join (
select Item, max(RunningTotal) five
from #RunTotal
where RunningTotal <= 5
group by Item
) b
on a.Item = b.Item
I've updated the accepted answer and got your desired result.
SELECT Item, SUM(Count)
FROM mytable t
GROUP BY Item
HAVING SUM(Count) <=5
UNION
SELECT Item, 5
FROM mytable t
GROUP BY Item
HAVING SUM(Count) >5
UNION
SELECT t2.Item + '>5', Sum(t2.Count) - 5
FROM mytable t2
GOUP BY Item
HAVING SUM(Count) > 5
ORDER BY 1, 2
select 'A' as Name, 2 as Cnt
into #tmp
union all select 'A',3
union all select 'A',3
union all select 'B',4
union all select 'B',4
union all select 'B',5
union all select 'C',1
select Name, case when sum(cnt) > 5 then 5 else sum(cnt) end Cnt
from #tmp
group by Name
union
select Name+'>5', sum(cnt)-5 Cnt
from #tmp
group by Name
having sum(cnt) > 5
Here is what I have so far. I know it's not complete but... this should be a good starting point.
I can get your second output by using a temp table and an update pass:
DECLARE #Data TABLE
(
ID INT IDENTITY(1,1) PRIMARY KEY
,Value VARCHAR(5)
,Number INT
,Total INT
)
INSERT INTO #Data (Value, Number) VALUES ('A',2)
INSERT INTO #Data (Value, Number) VALUES ('A',3)
INSERT INTO #Data (Value, Number) VALUES ('A',3)
INSERT INTO #Data (Value, Number) VALUES ('B',4)
INSERT INTO #Data (Value, Number) VALUES ('B',4)
INSERT INTO #Data (Value, Number) VALUES ('B',5)
INSERT INTO #Data (Value, Number) VALUES ('C',1)
DECLARE
#Value VARCHAR(5)
,#Count INT
UPDATE #Data
SET
#Count = Total = CASE WHEN Value = #Value THEN Number + #Count ELSE Number END
,#Value = Value
FROM #Data AS D
SELECT
Value
,Number
,Total
FROM #Data
There may be better ways, but this should work.

Generating a range of numbers in MySQL

How do I generate a range of consecutive numbers (one per line) from a MySQL query so that I can insert them into a table?
For example:
nr
1
2
3
4
5
I would like to use only MySQL for this (not PHP or other languages).
Here is one way to do it set-based without loops. This can also be made into a view for re-use. The example shows the generation of a sequence from 0 through 999, but of course, it may be modified to suit.
INSERT INTO
myTable
(
nr
)
SELECT
SEQ.SeqValue
FROM
(
SELECT
(HUNDREDS.SeqValue + TENS.SeqValue + ONES.SeqValue) SeqValue
FROM
(
SELECT 0 SeqValue
UNION ALL
SELECT 1 SeqValue
UNION ALL
SELECT 2 SeqValue
UNION ALL
SELECT 3 SeqValue
UNION ALL
SELECT 4 SeqValue
UNION ALL
SELECT 5 SeqValue
UNION ALL
SELECT 6 SeqValue
UNION ALL
SELECT 7 SeqValue
UNION ALL
SELECT 8 SeqValue
UNION ALL
SELECT 9 SeqValue
) ONES
CROSS JOIN
(
SELECT 0 SeqValue
UNION ALL
SELECT 10 SeqValue
UNION ALL
SELECT 20 SeqValue
UNION ALL
SELECT 30 SeqValue
UNION ALL
SELECT 40 SeqValue
UNION ALL
SELECT 50 SeqValue
UNION ALL
SELECT 60 SeqValue
UNION ALL
SELECT 70 SeqValue
UNION ALL
SELECT 80 SeqValue
UNION ALL
SELECT 90 SeqValue
) TENS
CROSS JOIN
(
SELECT 0 SeqValue
UNION ALL
SELECT 100 SeqValue
UNION ALL
SELECT 200 SeqValue
UNION ALL
SELECT 300 SeqValue
UNION ALL
SELECT 400 SeqValue
UNION ALL
SELECT 500 SeqValue
UNION ALL
SELECT 600 SeqValue
UNION ALL
SELECT 700 SeqValue
UNION ALL
SELECT 800 SeqValue
UNION ALL
SELECT 900 SeqValue
) HUNDREDS
) SEQ
Here's a hardware engineer's version of Pittsburgh DBA's solution:
SELECT
(TWO_1.SeqValue + TWO_2.SeqValue + TWO_4.SeqValue + TWO_8.SeqValue + TWO_16.SeqValue) SeqValue
FROM
(SELECT 0 SeqValue UNION ALL SELECT 1 SeqValue) TWO_1
CROSS JOIN (SELECT 0 SeqValue UNION ALL SELECT 2 SeqValue) TWO_2
CROSS JOIN (SELECT 0 SeqValue UNION ALL SELECT 4 SeqValue) TWO_4
CROSS JOIN (SELECT 0 SeqValue UNION ALL SELECT 8 SeqValue) TWO_8
CROSS JOIN (SELECT 0 SeqValue UNION ALL SELECT 16 SeqValue) TWO_16;
If you need the records in a table and you want to avoid concurrency issues, here's how to do it.
First you create a table in which to store your records
CREATE TABLE `incr` (
`Id` int(11) NOT NULL auto_increment,
PRIMARY KEY (`Id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
Secondly create a stored procedure like this:
DELIMITER ;;
CREATE PROCEDURE dowhile()
BEGIN
DECLARE v1 INT DEFAULT 5;
WHILE v1 > 0 DO
INSERT incr VALUES (NULL);
SET v1 = v1 - 1;
END WHILE;
END;;
DELIMITER ;
Lastly call the SP:
CALL dowhile();
SELECT * FROM incr;
Result
Id
1
2
3
4
5
Let's say you want to insert numbers 1 through 100 into your table. As long as you have some other table that has at least that many rows (doesn't matter the content of the table), then this is my preferred method:
INSERT INTO pivot100
SELECT #ROW := #ROW + 1 AS ROW
FROM someOtherTable t
join (SELECT #ROW := 0) t2
LIMIT 100
;
Want a range that starts with something other than 1? Just change what #ROW gets set to on the join.
As you all understand, this is rather hacky so use with care
SELECT
id % 12 + 1 as one_to_twelve
FROM
any_large_table
GROUP BY
one_to_twelve
;
DECLARE i INT DEFAULT 0;
WHILE i < 6 DO
/* insert into table... */
SET i = i + 1;
END WHILE;
Very similar to the accepted response, but using the new WITH syntax for mysql >= 8.0 which makes a lot more legible and the intent is also clearer
WITH DIGITS (N) AS (
SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL
SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL
SELECT 8 UNION ALL SELECT 9)
SELECT
UNITS.N + TENS.N*10 + HUNDREDS.N*100 + THOUSANDS.N*1000
FROM
DIGITS AS UNITS, DIGITS AS TENS, DIGITS AS HUNDREDS, DIGITS AS THOUSANDS;
In MariaDB you can do:
SELECT * FROM seq_i_to_N
For example:
SELECT * FROM seq_0_to_1000
SELECT * FROM seq_1_to_1000000
Reference: https://www.percona.com/blog/2020/07/27/generating-numeric-sequences-in-mysql/
The "shortest" way i know (in MySQL) to create a table with a long sequence is to (cross) join an existing table with itself. Since any (common) MySQL server has the information_schema.COLUMNS table i would use it:
DROP TABLE IF EXISTS seq;
CREATE TABLE seq (i MEDIUMINT AUTO_INCREMENT PRIMARY KEY)
SELECT NULL AS i
FROM information_schema.COLUMNS t1
JOIN information_schema.COLUMNS t2
JOIN information_schema.COLUMNS t3
LIMIT 100000; -- <- set your limit here
Usually one join should be enough to create over 1M rows - But one more join will not hurt :-) - Just don't forget to set a limit.
If you want to include 0, you should "remove" the AUTO_INCEMENT property.
ALTER TABLE seq ALTER i DROP DEFAULT;
ALTER TABLE seq MODIFY i MEDIUMINT;
Now you can insert 0
INSERT INTO seq (i) VALUES (0);
and negative numbers as well
INSERT INTO seq (i) SELECT -i FROM seq WHERE i <> 0;
You can validate the numbers with
SELECT MIN(i), MAX(i), COUNT(*) FROM seq;
All other answers are good, however they all have speed issues for larger ranges because they force MySQL to generate every number then filter them.
The following only makes MySQL generate the numbers that are needed, and therefore is faster:
set #amount = 55; # How many numbers from zero you want to generate
select `t0`.`i`+`t1`.`i`+`t2`.`i`+`t3`.`i` as `offset`
from
(select 0 `i` 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) `t0`,
(select 0 `i` union select 10 union select 20 union select 30 union select 40 union select 50 union select 60 union select 70 union select 80 union select 90) `t1`,
(select 0 `i` union select 100 union select 200 union select 300 union select 400 union select 500 union select 600 union select 700 union select 800 union select 900) `t2`,
(select 0 `i` union select 1000 union select 2000 union select 3000 union select 4000 union select 5000 union select 6000 union select 7000 union select 8000 union select 9000) `t3`
where `t3`.`i`<#amount
and `t2`.`i`<#amount
and `t1`.`i`<#amount
and `t0`.`i`+`t1`.`i`+`t2`.`i`+`t3`.`i`<#amount;
With the above you can generate upto 10,000 numbers (0 to 9,999) with no slower speed overhead for lower numbers, regardless how low they are.
Here's a way to do it with json_table if you have MySql 8 and above:
set #noRows = 100;
SELECT tt.rowid - 1 AS value
FROM JSON_TABLE(CONCAT('[{}', REPEAT(',{}', #noRows - 1), ']'),
"$[*]" COLUMNS(rowid FOR ORDINALITY)
) AS tt;
(h/t - https://www.percona.com/blog/2020/07/27/generating-numeric-sequences-in-mysql/)
The idea I want to share is not a precise response for the question but can be useful for some so I would like to share it.
If you frequently need only a limited set of numbers then it can be beneficial to create a table with the numbers you may need and just use that table every time. For example:
CREATE TABLE _numbers (num int);
INSERT _numbers VALUES (0), (1), (2), (3), ...;
This can be applied only if you need numbers below a certain reasonable limit, so don't use it for generating sequence 1...1 million but can be used for numbers 1...10k, for example.
If you have this list of numbers in the _numbers table then you can write queries like this, for obtaining the individual characters of a string:
SELECT number, substr(name, num, 1)
FROM users
JOIN _numbers ON num < length(name)
WHERE user_id = 1234
ORDER BY num;
If you need larger numbers than 10k then you can join the table to itself:
SELECT n1.num * 10000 + n2.num
FROM _numbers n1
JOIN _numbers n2
WHERE n1 < 100
ORDER BY n1.num * 10000 + n2.num; -- or just ORDER BY 1 meaning the first column
This is based on a previous answer (https://stackoverflow.com/a/53125278/2009581), but is compatible with MySQL 5.7. It works for replicas and read-only users:
SELECT x1.N + x10.N*10 + x100.N*100 + x1000.N*1000
FROM (SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) x1,
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) x10,
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) x100,
(SELECT 0 AS N UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) x1000
WHERE x1.N + x10.N*10 + x100.N*100 + x1000.N*1000 <= #max;
It generates integers in the range of [0, #max].
with recursive cte..
with recursive rnums as (
select 1 as n
union all
select n+1 as n from rnums
where n <10
)
select * from rnums
;
Result would be..
+------+
| n |
+------+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
+------+
10 rows in set (0.00 sec)