How to get Output parameter from a column in table for a stored procedure - sql

I've stored procedure like this:
create procedure sp_testsp
(
#vc_order_by varchar(100),
#int_start_index INT,
#int_grid_size INT,
#count bigint output
)
as
begin
select * from
(select ROW_NUMBER() over
(order by
case #vc_order_by = '' then tab1.int_id end desc) AS row,
*,
COUNT(tab1.int_id) OVER() as totalRowCount
from
(select * from tbl_test) tab1) tab2
where row BETWEEN CONVERT(VARCHAR, #int_start_index) and CONVERT(VARCHAR,(#int_start_index-1) + #int_grid_size);
set #count = 0;
end
We can execute the above stored procedure by:
DECLARE #size bigint;
EXEC sp_testsp '', 1,5, #size output;
SELECT #size;
The written sp provides data based on pagination and we can retrieve 100 or any number of records by passing a number in #int_grid_size .
The table output looks like following:
row int_id vc_name totalRowCount
1 5 a 107
2 6 ab 107
3 7 abc 107
4 8 abcd 107
5 10 abcc 107
The last column gives the total records count of the table or total record if we use where condition.
I want to OUTPUT any one column value of the totalRowCount in '#count' in the stored procedure.
I cannot use ##ROWCOUNT as it only sends the count of records the sp is outputting i.e in this case 5 but actual records are 107.
Just wondering if there is any way. Any help is apperciated. Thanks.
Edit:
I tried something like this, and it works:
create procedure sp_testsp
#param1 nvarchar(800),
#count bigint output
as
begin
select * from tbl_test tt where tt.col1 = #param1;
set #count = select Count(*) from tbl_test tt where tt.col1 = #param1;
end
The issue with this is I've to call the query once and then call the query again for #count. This is working but taking lot of time for big queries.

You can do that by temp table
select * into #temp from
(select ROW_NUMBER() over
(order by
case #vc_order_by = '' then tab1.int_id end desc) AS row,
*,
COUNT(tab1.int_id) OVER() as totalRowCount
from
(select * from tbl_test) tab1) tab2
where row BETWEEN CONVERT(VARCHAR, #int_start_index) and CONVERT(VARCHAR,(#int_start_index-1) + #int_grid_size);
select top 1 #count=totalRowCount from #temp
select * from #temp --you can exclude totalRowCount

Related

How do I return lets say 100 distinct ticket Id data from a view which returns two records per ticket Id using pagination?

I have a view that returns two columns Ticket_Id and Price. Each ticket can have up to 2 different prices. Along with this, I have a stored procedure that returns the data from the view to the caller based on input parameters for pagination.
#page : indicates the page number
#pageSize : indicates the number of records per page.
When a user requests 100 (unique tickets)rows I will have to return at most 200 rows of data.
For which i am using pagination as follows
OFFSET ', #pageSize,' * (',#page,' - 1) ROWS FETCH NEXT ', #pageSize,' ROWS ONLY
But it returns only 100 rows of data including duplicates. Is there a way I can modify the pagination parameters to retrieve all 200 rows of data ?
Example :
view returns as follows :
ticket_id
price
ticket1
10
ticket1
12
ticket2
11
ticket2
13
ticket3
12
ticket3
14
when the user requests with the input parameters:
#page = 1 , #PageSize = 3
I need to return all 6 rows of data.
View(Using view because stored procedure dosent not have access to tickets table directly)
select tck.ticket_id, tck.cost as 'price'
--,RANK() OVER(ORDER BY tck.ticket_id) 'Rank'
from tickets tck with (NOLOCK)
Store procedure:
ALTER PROCEDURE [dbo].[p_trans_history_srch]
-- Add the parameters for the stored procedure here
#page int=1, --optional
#pageSize int=20 --optional
AS
BEGIN
declare #finalsqlstmt nvarchar(max)
declare #pageString nvarchar(max)
declare #pageCount nvarchar(max) = ''
declare #viewName nvarchar(max)
set #pageString =concat(' OFFSET ', #pageSize,' * (',#page,' - 1) ROWS FETCH NEXT ', #pageSize,' ROWS ONLY')
set #finalsqlstmt = concat('select * from ',dbo.f_get_dbname(),#viewName,'where ',#search ,' and created_date between ''',#startDate,''' and ''',#endDate,''' order by created_date desc ',#pageString)
set #pageCount =concat('select count(distinct ticket_id) from ',dbo.f_get_dbname(),#viewName,'where ',#search,' and created_date between ''',#startDate,''' and ''',#endDate,'''' )
exec (#finalsqlstmt)
exec (#pageCount)
END
Note: I tried using RANK() OVER(ORDER BY ticket_id) 'Rank' and returning data based on rank, but because of the huge table size the performance of the query reduced drastically.
You can get "100 rows" if you pivot your two prices into two columns (for example "A" and "B"). I don't know if this is an option in your situation but here is an example:
DECLARE #t6 TABLE (A VARCHAR(100), B INT)
INSERT INTO #t6 (A,B)
SELECT 'ticket1',10
UNION ALL SELECT 'ticket1',12
UNION ALL SELECT 'ticket2',11
UNION ALL SELECT 'ticket2',13
UNION ALL SELECT 'ticket3',12
UNION ALL SELECT 'ticket3',14
;WITH cte_topivot AS
(
SELECT
[A],
CASE WHEN ROW_NUMBER() OVER (PARTITION BY A ORDER BY B ASC) = 1
THEN 'ticketA' ELSE 'ticketB' END [pivotCol],
[B]
FROM #t6 t
)
SELECT p.*
FROM cte_toPivot tp
PIVOT(MIN(B) FOR pivotCol IN ([ticketA],[ticketB])) p
Otherwise, if you are always getting exactly half the rows you want, can you multiply the user supplied page size by 2?

SQL Server - loop through table and update based on count

I have a SQL Server database. I need to loop through a table to get the count of each value in the column 'RevID'. Each value should only be in the table a certain number of times - for example 125 times. If the count of the value is greater than 125 or less than 125, I need to update the column to ensure all values in the RevID (are over 25 different values) is within the same range of 125 (ok to be a few numbers off)
For example, the count of RevID = "A2" is = 45 and the count of RevID = 'B2' is = 165 then I need to update RevID so the 45 count increases and the 165 decreases until they are within the 125 range.
This is what I have so far:
DECLARE #i INT = 1,
#RevCnt INT = SELECT RevId, COUNT(RevId) FROM MyTable group by RevId
WHILE(#RevCnt >= 50)
BEGIN
UPDATE MyTable
SET RevID= (SELECT COUNT(RevID) FROM MyTable)
WHERE RevID < 50)
#i = #i + 1
END
I have also played around with a cursor and instead of trigger. Any idea on how to achieve this? Thanks for any input.
Okay I cam back to this because I found it interesting even though clearly there are some business rules/discussion that you and I and others are not seeing. anyway, if you want to evenly and distribute arbitrarily there are a few ways you could do it by building recursive Common Table Expressions [CTE] or by building temp tables and more. Anyway here is a way that I decided to give it a try, I did utilize 1 temp table because sql was throwing in a little inconsistency with the main logic table as a cte about every 10th time but the temp table seems to have cleared that up. Anyway, this will evenly spread RevId arbitrarily and randomly assigning any remainder (# of Records / # of RevIds) to one of the RevIds. This script also doesn't rely on having a UniqueID or anything it works dynamically over row numbers it creates..... here you go just subtract out test data etc and you have what you more than likely want. Though rebuilding the table/values would probably be easier.
--Build Some Test Data
DECLARE #Table AS TABLE (RevId VARCHAR(10))
DECLARE #C AS INT = 1
WHILE #C <= 400
BEGIN
IF #C <= 200
BEGIN
INSERT INTO #Table (RevId) VALUES ('A1')
END
IF #c <= 170
BEGIN
INSERT INTO #Table (RevId) VALUES ('B2')
END
IF #c <= 100
BEGIN
INSERT INTO #Table (RevId) VALUES ('C3')
END
IF #c <= 400
BEGIN
INSERT INTO #Table (RevId) VALUES ('D4')
END
IF #c <= 1
BEGIN
INSERT INTO #Table (RevId) VALUES ('E5')
END
SET #C = #C+ 1
END
--save starting counts of test data to temp table to compare with later
IF OBJECT_ID('tempdb..#StartingCounts') IS NOT NULL
BEGIN
DROP TABLE #StartingCounts
END
SELECT
RevId
,COUNT(*) as Occurences
INTO #StartingCounts
FROM
#Table
GROUP BY
RevId
ORDER BY
RevId
/************************ This is the main method **********************************/
--clear temp table that is the main processing logic
IF OBJECT_ID('tempdb..#RowNumsToChange') IS NOT NULL
BEGIN
DROP TABLE #RowNumsToChange
END
--figure out how many records there are and how many there should be for each RevId
;WITH cteTargetNumbers AS (
SELECT
RevId
--,COUNT(*) as RevIdCount
--,SUM(COUNT(*)) OVER (PARTITION BY 1) / COUNT(*) OVER (PARTITION BY 1) +
--CASE
--WHEN ROW_NUMBER() OVER (PARTITION BY 1 ORDER BY NEWID()) <=
--SUM(COUNT(*)) OVER (PARTITION BY 1) % COUNT(*) OVER (PARTITION BY 1)
--THEN 1
--ELSE 0
--END as TargetNumOfRecords
,SUM(COUNT(*)) OVER (PARTITION BY 1) / COUNT(*) OVER (PARTITION BY 1) +
CASE
WHEN ROW_NUMBER() OVER (PARTITION BY 1 ORDER BY NEWID()) <=
SUM(COUNT(*)) OVER (PARTITION BY 1) % COUNT(*) OVER (PARTITION BY 1)
THEN 1
ELSE 0
END - COUNT(*) AS NumRecordsToUpdate
FROM
#Table
GROUP BY
RevId
)
, cteEndRowNumsToChange AS (
SELECT *
,SUM(CASE WHEN NumRecordsToUpdate > 1 THEN NumRecordsToUpdate ELSE 0 END)
OVER (PARTITION BY 1 ORDER BY RevId) AS ChangeEndRowNum
FROM
cteTargetNumbers
)
SELECT
*
,LAG(ChangeEndRowNum,1,0) OVER (PARTITION BY 1 ORDER BY RevId) as ChangeStartRowNum
INTO #RowNumsToChange
FROM
cteEndRowNumsToChange
;WITH cteOriginalTableRowNum AS (
SELECT
RevId
,ROW_NUMBER() OVER (PARTITION BY RevId ORDER BY (SELECT 0)) as RowNumByRevId
FROM
#Table t
)
, cteRecordsAllowedToChange AS (
SELECT
o.RevId
,o.RowNumByRevId
,ROW_NUMBER() OVER (PARTITION BY 1 ORDER BY (SELECT 0)) as ChangeRowNum
FROM
cteOriginalTableRowNum o
INNER JOIN #RowNumsToChange t
ON o.RevId = t.RevId
AND t.NumRecordsToUpdate < 0
AND o.RowNumByRevId <= ABS(t.NumRecordsToUpdate)
)
UPDATE o
SET RevId = u.RevId
FROM
cteOriginalTableRowNum o
INNER JOIN cteRecordsAllowedToChange c
ON o.RevId = c.RevId
AND o.RowNumByRevId = c.RowNumByRevId
INNER JOIN #RowNumsToChange u
ON c.ChangeRowNum > u.ChangeStartRowNum
AND c.ChangeRowNum <= u.ChangeEndRowNum
AND u.NumRecordsToUpdate > 0
IF OBJECT_ID('tempdb..#RowNumsToChange') IS NOT NULL
BEGIN
DROP TABLE #RowNumsToChange
END
/***************************** End of Main Method *******************************/
-- Compare the results and clean up
;WITH ctePostUpdateResults AS (
SELECT
RevId
,COUNT(*) as AfterChangeOccurences
FROM
#Table
GROUP BY
RevId
)
SELECT *
FROM
#StartingCounts s
INNER JOIN ctePostUpdateResults r
ON s.RevId = r.RevId
ORDER BY
s.RevId
IF OBJECT_ID('tempdb..#StartingCounts') IS NOT NULL
BEGIN
DROP TABLE #StartingCounts
END
Since you've given no rules for how you'd like the balance to operate we're left to speculate. Here's an approach that would find the most overrepresented value and then find an underrepresented value that can take on the entire overage.
I have no idea how optimal this is and it will probably run in an infinite loop without more logic.
declare #balance int = 125;
declare #cnt_over int;
declare #cnt_under int;
declare #revID_overrepresented varchar(32);
declare #revID_underrepresented varchar(32);
declare #rowcount int = 1;
while #rowcount > 0
begin
select top 1 #revID_overrepresented = RevID, #cnt_over = count(*)
from T
group by RevID
having count(*) > #balance
order by count(*) desc
select top 1 #revID_underrepresented = RevID, #cnt_under = count(*)
from T
group by RevID
having count(*) < #balance - #cnt_over
order by count(*) desc
update top #cnt_over - #balance T
set RevId = #revID_underrepresented
where RevId = #revID_overrepresented;
set #rowcount = ##rowcount;
end
The problem is I don't even know what you mean by balance...You say it needs to be evenly represented but it seems like you want it to be 125. 125 is not "even", it is just 125.
I can't tell what you are trying to do, but I'm guessing this is not really an SQL problem. But you can use SQL to help. Here is some helpful SQL for you. You can use this in your language of choice to solve the problem.
Find the rev values and their counts:
SELECT RevID, COUNT(*)
FROM MyTable
GROUP BY MyTable
Update #X rows (with RevID of value #RevID) to a new value #NewValue
UPDATE TOP #X FROM MyTable
SET RevID = #NewValue
WHERE RevID = #RevID
Using these two queries you should be able to apply your business rules (which you never specified) in a loop or whatever to change the data.

Update table with new value for each row

I need to update a column (type of datetime) in the top 1000 rows my table. However the catch is with each additional row I must increment the GETDATE() by 1 second... something like DATEADD(ss,1,GETDATE())
The only way I know how to do this is something like this:
UPDATE tablename
SET columnname = CASE id
WHEN 1 THEN DATEADD(ss,1,GETDATE())
WHEN 2 THEN DATEADD(ss,2,GETDATE())
...
END
Obviously this is not plausible. Any ideas?
How about using id rather than a constant?
UPDATE tablename
SET columnname = DATEADD(second, id, GETDATE() )
WHERE id <= 1000;
If you want the first 1000 rows (by id), but the id has gaps or other problems, then you can use a CTE:
with toupdate as (
select t.*, row_number() over (order by id) as seqnum
from tablename
)
update toupdate
set columnname = dateadd(second, seqnum, getdate())
where seqnum <= 1000;
I don't know what your ID is like, and I'm assuming you have at least SQL Server 2008 or else ROW_NUMBER() won't work.
Note: I did top 2 to show you that you that the top works. You can change it to top 1000 for your actual query.
DECLARE #table TABLE (ID int, columnName DATETIME);
INSERT INTO #table(ID)
VALUES(1),(2),(3);
UPDATE #table
SET columnName = DATEADD(SECOND,B.row_num,GETDATE())
FROM #table A
INNER JOIN
(
SELECT TOP 2 *, ROW_NUMBER() OVER (ORDER BY ID) row_num
FROM #table
ORDER BY ID
) B
ON A.ID = B.ID
SELECT *
FROM #table
Results:
ID columnName
----------- -----------------------
1 2015-03-31 13:11:59.760
2 2015-03-31 13:12:00.760
3 NULL
You don't make explicit the SQL Server version you're using, so I will assume SQL Server 2005 or above. I believe the WAITFOR DELAY command would be a good option to keep adding 1 second to each rows of the datetime column.
See this example:
-- Create temp table
CREATE TABLE #Client
(
RecordID int identity(1,1),
[Name] nvarchar(100) not null,
PurchaseDate datetime null
)
-- Fill in temp table with example values
INSERT INTO #Client
VALUES ( 'Jhon', NULL)
INSERT INTO #Client
VALUES ( 'Martha', NULL)
INSERT INTO #Client
VALUES ( 'Jimmy', NULL)
-- Create local variables
DECLARE #currentRecordId int,
#currentName nvarchar(100)
-- Create cursor
DECLARE ClientsCursor CURSOR FOR
SELECT RecordID,
[Name]
FROM #Client
OPEN ClientsCursor
FETCH FROM ClientsCursor INTO #currentRecordId, #currentName
-- Check ##FETCH_STATUS to see if there are any more rows to fetch.
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE #Client
SET PurchaseDate = DATEADD(ss,1,GETDATE())
WHERE RecordID = #currentRecordId
AND [Name] = #currentName
WAITFOR DELAY '00:00:01.000'
FETCH NEXT FROM ClientsCursor INTO #currentRecordId, #currentName
END
CLOSE ClientsCursor;
DEALLOCATE ClientsCursor;
SELECT *
FROM #Client
And here's the result:
1 Jhon 2015-03-31 15:20:04.477
2 Martha 2015-03-31 15:20:05.473
3 Jimmy 2015-03-31 15:20:06.470
Hope you find this answer helpful
This should be what you need (at least a guidline)
DELIMITER $$
CREATE PROCEDURE ADDTIME()
BEGIN
DECLARE i INT Default 1 ;
simple_loop: LOOP
UPDATE tablename SET columnname = DATE_ADD(NOW(), INTERVAL i SECOND) where rownumber = i
SET i=i+1;
IF i=1001 THEN
LEAVE simple_loop;
END IF;
END LOOP simple_loop;
END $$
To call that stored procedure use
CALL ADDTIME()

Populating an empty table with sequential numbers

I have a table which is already truncated (Microsoft SQL 2008). I have to now populate it with sequential numbers up to 50,000 records arbitrary numbers (doesn't mater) up to 7 characters.
Can any one help as to what SQL statement I need to write that will automatically populate the newly empty table with A000001,A0000002,A0000003, etc so that I can sort number the records within the table.
I have approximately 50000 records which I need to sequentially entered and I really don't want to number the column manually via hand editing.
Thanks in advance.
I'd use excel to generate your unique ids using the following:
In A column:
=CONCATENATE($C2, TEXT($B2,"000000"))
In B column put a 1 in the first row and the following code in all subsequent rows:
=SUM($B4 + 1)
In C column:
The letter A
Then just import the excel csv as a table and you'll have all your ids ready to insert into your empty table.
The SQL below loads a table variable up. Just select from it and insert the data into the new table. Certainly not the model of efficiency, but it'll get the job done.
DECLARE #tmp TABLE(
Value NVARCHAR(10)
)
DECLARE #Counter INT=0
DECLARE #Padding NVARCHAR(20)
WHILE #Counter<50000
BEGIN
SET #Counter=#Counter+1
SET #Padding=
CASE LEN(CONVERT(NVARCHAR,#Counter))
WHEN 1 THEN '00000'
WHEN 2 THEN '0000'
WHEN 3 THEN '000'
WHEN 4 THEN '00'
WHEN 5 THEN '0'
ELSE ''
END
INSERT INTO #tmp SELECT 'A' + #Padding + CONVERT(NVARCHAR,#Counter)
END
select * from #tmp
Use Stacked CTE to generate sequential Numbers
;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
), -- 10
e2(n) AS (SELECT 1 FROM e1 CROSS JOIN e1 AS b), -- 10*10
e3(n) AS (SELECT 1 FROM e2 CROSS JOIN e2 AS b), -- 100*100
e4(n) AS (SELECT 1 FROM e3 CROSS JOIN (SELECT TOP 5 n FROM e1) AS b) -- 5*10000
SELECT n = 'A'+right('000000'+
convert(varchar(20),ROW_NUMBER() OVER (ORDER BY n)),7)
FROM e4 ORDER BY n;
Check here for more methods to generate sequential numbers with performance analysis
Use a table with an identity column and populate it. Then update that table to set the alpha value you need as follows:
create table MyTable (
ID int not null identity(1,1),
Alpha varchar(30)
)
truncate table MyTable
begin tran -- makes it run much faster
declare #i int
select #i = 1
while #i < 1000000
begin
insert into MyTable (Alpha) values ('')
select #i = #i + 1
end
commit
update MyTable set Alpha = 'A' + replicate('0', 6 - len(cast(ID as varchar(30)))) + cast(ID as varchar(30))

T-Sql count string sequences over multiple rows

How can I find subsets of data over multiple rows in sql?
I want to count the number of occurrences of a string (or number) before another string is found and then count the number of times this string occurs before another one is found.
All these strings can be in random order.
This is what I want to achieve:
I have one table with one column (columnx) with data like this:
A
A
B
C
A
B
B
The result I want from the query should be like this:
2 A
1 B
1 C
1 A
2 B
Is this even possible in sql or would it be easier just to write a little C# app to do this?
Since, as per your comment, you can add a column that will unambiguously define the order in which the columnx values go, you can try the following query (provided the SQL product you are using supports CTEs and ranking functions):
WITH marked AS (
SELECT
columnx,
sortcolumn,
grp = ROW_NUMBER() OVER ( ORDER BY sortcolumn)
- ROW_NUMBER() OVER (PARTITION BY columnx ORDER BY sortcolumn)
FROM data
)
SELECT
columnx,
COUNT(*)
FROM marked
GROUP BY
columnx,
grp
ORDER BY
MIN(sortcolumn)
;
You can see the method in work on SQL Fiddle.
If sortcolumn is an auto-increment integer column that is guaranteed to have no gaps, you can replace the first ROW_NUMBER() expression with just sortcolumn. But, I guess, that cannot be guaranteed in general. Besides, you might indeed want to sort on a timestamp instead of an integer.
I dont think you can do it with a single select.
You can use AdventureWorks cursor:
create table my_Strings
(
my_string varchar(50)
)
insert into my_strings values('A'),('A'),('B'),('C'),('A'),('B'),('B') -- this method will only work on SQL Server 2008
--select my_String from my_strings
declare #temp_result table(
string varchar(50),
nr int)
declare #myString varchar(50)
declare #myLastString varchar(50)
declare #nr int
set #myLastString='A' --set this with the value of your FIRST string on the table
set #nr=0
DECLARE string_cursor CURSOR
FOR
SELECT my_string as aux_column FROM my_strings
OPEN string_cursor
FETCH NEXT FROM string_cursor into #myString
WHILE ##FETCH_STATUS = 0 BEGIN
if (#myString = #myLastString) begin
set #nr=#nr+1
set #myLastString=#myString
end else begin
insert into #temp_result values (#myLastString, #nr)
set #myLastString=#myString
set #nr=1
end
FETCH NEXT FROM string_cursor into #myString
END
insert into #temp_result values (#myLastString, #nr)
CLOSE string_cursor;
DEALLOCATE string_cursor;
select * from #temp_result
Result:
A 2
B 1
C 1
A 1
B 2
Try this :
;with sample as (
select 'A' as columnx
union all
select 'A'
union all
select 'B'
union all
select 'C'
union all
select 'A'
union all
select 'B'
union all
select 'B'
), data
as (
select columnx,
Row_Number() over(order by (select 0)) id
from sample
) , CTE as (
select * ,
Row_Number() over(order by (select 0)) rno from data
) , result as (
SELECT d.*
, ( SELECT MAX(ID)
FROM CTE c
WHERE NOT EXISTS (SELECT * FROM CTE
WHERE rno = c.rno-1 and columnx = c.columnx)
AND c.ID <= d.ID) AS g
FROM data d
)
SELECT columnx,
COUNT(1) cnt
FROM result
GROUP BY columnx,
g
Result :
columnx cnt
A 2
B 1
C 1
A 1
B 2