Alternative to SQL window functions in Sybase - sybase

I am working on Sybase Adaptive Server Enterprise (version 12.5.0.3). Trying to use Row_number() OVER (Partition by columnname order by columnname). When I execute the query it is throwing an exception saying that the syntax near OVER is incorrect. I have searched for proper row_number() syntax for sybase database, but there is nothing wrong in the syntax. I guess that the Sybase version that am using does not support row_number() OVER. I even tried dense_rank() OVER, but am getting the same error.
I need to know whether it is really a syntax issue or its because of Sybase's low version which is not supporting the key words?
If the issue is with the version, then is there any alternative for row_number OVER and dense_rank() OVER for sybase database.
My Query:
select cr.firstname, cr.lastname, cr.dob,cr.phone,
row_number() over (patition by cr.dob order by createddate) "rank"
from ff.CrossReferenceTable cr
Error Message:
Server Message: Number 156, Severity 15
Server 'MyServer', Line 1:
Incorrect syntax near the keyword 'over'.

Right, unfortunately Sybase ASE doesn't support row_number() function as well as rank() and dense_rank().
However, in some simple cases, where partition clause is not used it could be converted in the way like
select rank=identity(music), * into #new_temp_tab1 from CrossReferenceTable order by createddate
select firstname, lastname, dob, phone, rank from #new_temp_tab1
In your case it's going to be a little bit more complicated, I can recommend using cursor with temporary table to emulate row_number() over partition by behavior.
Please have a look at the example below:
create table CrossReferenceTable
(
firstname varchar(50),
lastname varchar(50),
dob int,
phone char(10),
createddate date
)
go
create proc sp_CrossReferenceTable
as
begin
declare #i int
declare #cur_firstname varchar(50)
declare #cur_lastname varchar(50)
declare #cur_dob int
declare #cur_phone varchar(10)
declare #cur_rank int
declare cur cursor for
select
cr.firstname,
cr.lastname,
cr.dob,
cr.phone,
count(*) AS "rank"
from
CrossReferenceTable cr
group by
cr.dob
order by
cr.dob,
createddate
CREATE TABLE #CrossReferenceTable_TEMP
(
firstname varchar(50),
lastname varchar(50),
dob int,
phone char(10),
rank INT
)
open cur
fetch cur into
#cur_firstname,
#cur_lastname,
#cur_dob,
#cur_phone,
#cur_rank
set #i = #cur_rank
while ##SQLSTATUS = 0
begin
if #i = 0
set #i = #cur_rank
insert into #CrossReferenceTable_TEMP
select
#cur_firstname,
#cur_lastname,
#cur_dob,
#cur_phone,
case
when #cur_rank > 1 then #cur_rank - (#i - 1)
ELSE #cur_rank
end as "rank"
set #i = #i - 1
fetch cur into
#cur_firstname,
#cur_lastname,
#cur_dob,
#cur_phone,
#cur_rank
end
select
firstname,
lastname,
dob,
phone,
rank
from
#CrossReferenceTable_TEMP
end
go
exec sp_CrossReferenceTable

Try the generic query below to have same effect as ROW_NUMBER()
SELECT
A.MyPartitionColumn,
A.MyRunningNumberColumn,
( SELECT count(*)
FROM MyTable
WHERE MyPartitionColumn = A.MyPartitionColumn
AND MyRunningNumberColumn <= A.MyRunningNumberColumn
) AS "Row_Number"
FROM MyTable A
ORDER BY MyPartitionColumn, MyRunningNumberColumn

Related

Error While OFFSET FETCH | SQL SERVER

I am trying to do pagination in SQL SERVER and I am getting two errors on OFFSET and ROWS
CREATE PROCEDURE XYZ
#offset int,
#limit int,
#order char(4),
#Id int
AS
BEGIN
SELECT * FROM TABLE
WHERE ID = #id
ORDER BY
CASE WHEN #order = 'desc' THEN [TIME] END DESC,
CASE WHEN #order = 'asc' THEN [TIME] END ASC
*OFFSET* #offset FETCH ROWS NEXT #limit *ROWS* ONLY
END
GO
Error on OFFSET:
Incorrect Syntax near 'OFFSET'.
Error on ROWS (Second one):
Incorrect Syntax near 'ROWS'. Expecting FROM.
Can someone help me out with this ?
Well, in SQL Server 2008 you obviously can't use the fetch...next clause that was introduced in 2012.
However, this doesn't mean you can't do pagination.
One simple way is to use a cte with row_number:
;WITH CTE AS
(
SELECT *, -- Don't be lazy, specify the Columns list...
ROW_NUMBER() OVER
(
ORDER BY
CASE WHEN #order = 'desc' THEN [TIME] END DESC,
CASE WHEN #order = 'asc' THEN [TIME] END ASC
) As rn
FROM TABLE
WHERE ID = #id
)
SELECT * -- Don't be lazy, specify the Columns list...
FROM CTE
WHERE rn >= #offset
AND rn <= #offset + #limit

TSQL - Optimizing Full Text Search query with temp table

To make it short I have a full text search query that does a company search on a single table. Once the search is complete I pull extra stats from the result such as Top 5 titles, top 5 locations etc...
How can this query be optimized it currently takes about 5 seconds to execute on < 25,000 rows and based on the execution plan its mostly on the last 3 select statements.
SQL SERVER: 2005. I can upgrade to 2008 but I've heard there are more performance issues with SQL 2008.
Help is greatly appreciated.
CREATE PROCEDURE [usp_Company_Search]
#KeywordNear as varchar(250),
#LocationNear as varchar(250) = null,
#PageIndex as int,
#Pagesize as int
AS
BEGIN
DECLARE #tbl TABLE
(
row int,
[Rank] int,
CompanyID int,
CompanyDesc text,
Title nvarchar(150),
Company nvarchar(150),
Category nvarchar(50),
Source nvarchar(50),
URI nvarchar(250),
Location varchar(60),
DateCreated nvarchar(50)
)
IF (#LocationNear is not null) BEGIN
WITH CompanySearch as
(
SELECT ROW_NUMBER() OVER (ORDER BY rs.rank desc) as row,
rs.Rank as [Rank],
J.CompanyID,
J.CompanyDesc,
J.Title,
J.Company,
J.Category,
J.Source,
J.URI,
J.Location,
J.DateCreated
FROM Company J
INNER JOIN
CONTAINSTABLE (Company,RawStripped, #KeywordNear) rs
ON J.Companyid = rs.[KEY] AND
CONTAINS (Location, #LocationNear)
)
insert into #tbl select * from CompanySearch
SELECT
CompanySearch.[Rank],
CompanySearch.CompanyID,
CompanySearch.CompanyDesc,
CompanySearch.Title,
CompanySearch.Company,
CompanySearch.Category,
CompanySearch.Source,
CompanySearch.URI,
CompanySearch.Location,
CompanySearch.DateCreated
FROM #tbl as CompanySearch
WHERE CompanySearch.row between (#PageIndex - 1) * #PageSize + 1 and #PageIndex*#PageSize
END
ELSE
BEGIN
WITH CompanySearch as
(
SELECT ROW_NUMBER() OVER (ORDER BY rs.rank desc) as row,
rs.Rank,
J.CompanyID,
J.CompanyDesc,
J.Title,
J.Company,
J.Category,
J.Source,
J.URI,
J.Location,
J.DateCreated
FROM Company J
INNER JOIN
CONTAINSTABLE (Company,RawStripped, #KeywordNear) rs
ON J.Companyid = rs.[KEY]
)
insert into #tbl select * from CompanySearch
SELECT
CompanySearch.Rank,
CompanySearch.CompanyID,
CompanySearch.CompanyDesc,
CompanySearch.Title,
CompanySearch.Company,
CompanySearch.Category,
CompanySearch.Source,
CompanySearch.URI,
CompanySearch.Location,
CompanySearch.DateCreated
FROM #tbl as CompanySearch
WHERE CompanySearch.row between (#PageIndex - 1) * #PageSize + 1 and #PageIndex*#PageSize
END
SELECT Max(row) as RecordCount from #tbl
select top 5 title, count(title) as cnt from #tbl group by title order by cnt desc
SELECT top 5 Location, count(location) as cnt from #tbl group by location order by cnt desc
SELECT top 5 Company, count(company) as cnt from #tbl group by company order by cnt desc
END
Your execution plan results may be deceiving. In SQL 2005, the fulltext engine is an external service, so SQL cannot accurately report on what's happening in that piece of the puzzle.
I'm not sure what performance issues you've heard of in 2008, but in 2008 the fulltext engine becomes fully integrated with the database, making it much more efficient in a case like yours where you're joining a database table against a set of fulltext results. If upgrading is an option for you, I'd encourage you to pursue that option.
See: SQL Server 2008 Full-Text Search: Internals and Enhancements

SQL Server Full Text Search Very Slow

I have a stored procedure that searches a table which has about 200000+ rows with full text FREETEXT.
Here is the basics of it:
declare #searchKey varchar(150)
if #searchKey Is Null OR LEN(#searchKey)=0
Set #searchKey='""';
Set #searchKey='car';
declare #perPage int
Set #perPage=40
declare #pageNo int
Set #pageNo=1
declare #startIndex int,#endIndex int;
Set #startIndex=#perPage*#pageNo-#perPage+1;
Set #endIndex=#perPage*#pageNo;
Select totalItems
--i pull other colums as well
from (
Select Row_Number() over(order by CreateDate DESC) As rowNumber
,COUNT(*) OVER() as totalItems
--other columns are pulled as well
from MyTable P
Where
#searchKey='""'
OR FreeText((P.Title,P.Description),#searchKey)
) tempData
--where rowNumber>=#startIndex AND rowNumber<=#endIndex
where
rowNumber>=CASE WHEN #startIndex>0 AND #endIndex>0 THEN #startIndex ELSE rowNumber END
AND rowNumber<=CASE WHEN #startIndex>0 AND #endIndex>0 THEN #endIndex ELSE rowNumber END
order by rowNumber
The problem is its running slower then i would like it. Its taking about 3 seconds to load the page. Same page was loading in less then 1 sec when i was using like operator.
In my experience, full text index functions do not work well in a clause that contains an "OR" operator. I have had to get the same behavior by adjusting my query to use a UNION. Try this and see if you can get better performance.
declare #searchKey varchar(150)
if #searchKey Is Null OR LEN(#searchKey)=0
Set #searchKey='""';
Set #searchKey='car';
declare #perPage int
Set #perPage=40
declare #pageNo int
Set #pageNo=1
declare #startIndex int,#endIndex int;
Set #startIndex=#perPage*#pageNo-#perPage+1;
Set #endIndex=#perPage*#pageNo;
Select totalItems
--i pull other colums as well
from (
Select Row_Number() over(order by CreateDate DESC) As rowNumber
,COUNT(*) OVER() as totalItems
--other columns are pulled as well
from
(
select * from
MyTable A
Where
#searchKey='""'
UNION
select * from MyTable B
where FreeText((B.Title,B.Description),#searchKey)
) as innerTable
) tempData
--where rowNumber>=#startIndex AND rowNumber<=#endIndex
where
rowNumber>=CASE WHEN #startIndex>0 AND #endIndex>0 THEN #startIndex ELSE rowNumber END
AND rowNumber<=CASE WHEN #startIndex>0 AND #endIndex>0 THEN #endIndex ELSE rowNumber END
order by rowNumber

SQL Server: UPDATE a table by using ORDER BY

I would like to know if there is a way to use an order by clause when updating a table. I am updating a table and setting a consecutive number, that's why the order of the update is important. Using the following sql statement, I was able to solve it without using a cursor:
DECLARE #Number INT = 0
UPDATE Test
SET #Number = Number = #Number +1
now what I'd like to to do is an order by clause like so:
DECLARE #Number INT = 0
UPDATE Test
SET #Number = Number = #Number +1
ORDER BY Test.Id DESC
I've read: How to update and order by using ms sql The solutions to this question do not solve the ordering problem - they just filter the items on which the update is applied.
Take care,
Martin
No.
Not a documented 100% supported way. There is an approach sometimes used for calculating running totals called "quirky update" that suggests that it might update in order of clustered index if certain conditions are met but as far as I know this relies completely on empirical observation rather than any guarantee.
But what version of SQL Server are you on? If SQL2005+ you might be able to do something with row_number and a CTE (You can update the CTE)
With cte As
(
SELECT id,Number,
ROW_NUMBER() OVER (ORDER BY id DESC) AS RN
FROM Test
)
UPDATE cte SET Number=RN
You can not use ORDER BY as part of the UPDATE statement (you can use in sub-selects that are part of the update).
UPDATE Test
SET Number = rowNumber
FROM Test
INNER JOIN
(SELECT ID, row_number() OVER (ORDER BY ID DESC) as rowNumber
FROM Test) drRowNumbers ON drRowNumbers.ID = Test.ID
Edit
Following solution could have problems with clustered indexes involved as mentioned here. Thanks to Martin for pointing this out.
The answer is kept to educate those (like me) who don't know all side-effects or ins and outs of SQL Server.
Expanding on the answer gaven by Quassnoi in your link, following works
DECLARE #Test TABLE (Number INTEGER, AText VARCHAR(2), ID INTEGER)
DECLARE #Number INT
INSERT INTO #Test VALUES (1, 'A', 1)
INSERT INTO #Test VALUES (2, 'B', 2)
INSERT INTO #Test VALUES (1, 'E', 5)
INSERT INTO #Test VALUES (3, 'C', 3)
INSERT INTO #Test VALUES (2, 'D', 4)
SET #Number = 0
;WITH q AS (
SELECT TOP 1000000 *
FROM #Test
ORDER BY
ID
)
UPDATE q
SET #Number = Number = #Number + 1
The row_number() function would be the best approach to this problem.
UPDATE T
SET T.Number = R.rowNum
FROM Test T
JOIN (
SELECT T2.id,row_number() over (order by T2.Id desc) rowNum from Test T2
) R on T.id=R.id
update based on Ordering by the order of values in a SQL IN() clause
Solution:
DECLARE #counter int
SET #counter = 0
;WITH q AS
(
select * from Products WHERE ID in (SELECT TOP (10) ID FROM Products WHERE ID IN( 3,2,1)
ORDER BY ID DESC)
)
update q set Display= #counter, #counter = #counter + 1
This updates based on descending 3,2,1
Hope helps someone.
I had a similar problem and solved it using ROW_NUMBER() in combination with the OVER keyword. The task was to retrospectively populate a new TicketNo (integer) field in a simple table based on the original CreatedDate, and grouped by ModuleId - so that ticket numbers started at 1 within each Module group and incremented by date. The table already had a TicketID primary key (a GUID).
Here's the SQL:
UPDATE Tickets SET TicketNo=T2.RowNo
FROM Tickets
INNER JOIN
(select TicketID, TicketNo,
ROW_NUMBER() OVER (PARTITION BY ModuleId ORDER BY DateCreated) AS RowNo from Tickets)
AS T2 ON T2.TicketID = Tickets.TicketID
Worked a treat!
I ran into the same problem and was able to resolve it in very powerful way that allows unlimited sorting possibilities.
I created a View using (saving) 2 sort orders (*explanation on how to do so below).
After that I simply applied the update queries to the View created and it worked great.
Here are the 2 queries I used on the view:
1st Query:
Update MyView
Set SortID=0
2nd Query:
DECLARE #sortID int
SET #sortID = 0
UPDATE MyView
SET #sortID = sortID = #sortID + 1
*To be able to save the sorting on the View I put TOP into the SELECT statement. This very useful workaround allows the View results to be returned sorted as set when the View was created when the View is opened. In my case it looked like:
(NOTE: Using this workaround will place an big load on the server if using a large table and it is therefore recommended to include as few fields as possible in the view if working with large tables)
SELECT TOP (600000)
dbo.Items.ID, dbo.Items.Code, dbo.Items.SortID, dbo.Supplier.Date,
dbo.Supplier.Code AS Expr1
FROM dbo.Items INNER JOIN
dbo.Supplier ON dbo.Items.SupplierCode = dbo.Supplier.Code
ORDER BY dbo.Supplier.Date, dbo.Items.ID DESC
Running: SQL Server 2005 on a Windows Server 2003
Additional Keywords: How to Update a SQL column with Ascending or Descending Numbers - Numeric Values / how to set order in SQL update statement / how to save order by in sql view / increment sql update / auto autoincrement sql update / create sql field with ascending numbers
SET #pos := 0;
UPDATE TABLE_NAME SET Roll_No = ( SELECT #pos := #pos + 1 ) ORDER BY First_Name ASC;
In the above example query simply update the student Roll_No column depending on the student Frist_Name column. From 1 to No_of_records in the table. I hope it's clear now.
IF OBJECT_ID('tempdb..#TAB') IS NOT NULL
BEGIN
DROP TABLE #TAB
END
CREATE TABLE #TAB(CH1 INT,CH2 INT,CH3 INT)
DECLARE #CH2 INT = NULL , #CH3 INT=NULL,#SPID INT=NULL,#SQL NVARCHAR(4000)='', #ParmDefinition NVARCHAR(50)= '',
#RET_MESSAGE AS VARCHAR(8000)='',#RET_ERROR INT=0
SET #ParmDefinition='#SPID INT,#CH2 INT OUTPUT,#CH3 INT OUTPUT'
SET #SQL='UPDATE T
SET CH1=#SPID,#CH2= T.CH2,#CH3= T.CH3
FROM #TAB T WITH(ROWLOCK)
INNER JOIN (
SELECT TOP(1) CH1,CH2,CH3
FROM
#TAB WITH(NOLOCK)
WHERE CH1 IS NULL
ORDER BY CH2 DESC) V ON T.CH2= V.CH2 AND T.CH3= V.CH3'
INSERT INTO #TAB
(CH2 ,CH3 )
SELECT 1,2 UNION ALL
SELECT 2,3 UNION ALL
SELECT 3,4
BEGIN TRY
WHILE EXISTS(SELECT TOP 1 1 FROM #TAB WHERE CH1 IS NULL)
BEGIN
EXECUTE #RET_ERROR = sp_executesql #SQL, #ParmDefinition,#SPID =##SPID, #CH2=#CH2 OUTPUT,#CH3=#CH3 OUTPUT;
SELECT * FROM #TAB
SELECT #CH2,#CH3
END
END TRY
BEGIN CATCH
SET #RET_ERROR=ERROR_NUMBER()
SET #RET_MESSAGE = '#ERROR_NUMBER : ' + CAST(ERROR_NUMBER() AS VARCHAR(255)) + '#ERROR_SEVERITY :' + CAST( ERROR_SEVERITY() AS VARCHAR(255))
+ '#ERROR_STATE :' + CAST(ERROR_STATE() AS VARCHAR(255)) + '#ERROR_LINE :' + CAST( ERROR_LINE() AS VARCHAR(255))
+ '#ERROR_MESSAGE :' + ERROR_MESSAGE() ;
SELECT #RET_ERROR,#RET_MESSAGE;
END CATCH

Can I use #table variable in SQL Server Report Builder?

Using SQL Server 2008 Reporting services:
I'm trying to write a report that displays some correlated data so I thought to use a #table variable like so
DECLARE #Results TABLE (Number int
,Name nvarchar(250)
,Total1 money
,Total2 money
)
insert into #Results(Number, Name, Total1)
select number, name, sum(total)
from table1
group by number, name
update #Results
set total2 = total
from
(select number, sum(total) from table2) s
where s.number = number
select from #results
However, Report Builder keeps asking to enter a value for the variable #Results. It this at all possible?
EDIT: As suggested by KM I've used a stored procedure to solve my immediate problem, but the original question still stands: can I use #table variables in Report Builder?
No.
ReportBuilder will
2nd guess you
treats #Results as a parameter
Put all of that in a stored procedure and have report builder call that procedure. If you have many rows to process you might be better off (performance wise) with a #temp table where you create a clustered primary key on Number (or would it be Number+Name, not sure of your example code).
EDIT
you could try to do everything in one SELECT and send that to report builder, this should be the fastest (no temp tables):
select
dt.number, dt.name, dt.total1, s.total2
from (select
number, name, sum(total) AS total1
from table1
group by number, name
) dt
LEFT OUTER JOIN (select
number, sum(total) AS total2
from table2
GROUP BY number --<<OP code didn't have this, but is it needed??
) s ON dt.number=s.number
I've seen this problem as well. It seems SQLRS is a bit case-sensitive. If you ensure that your table variable is declared and referenced everywhere with the same letter case, you will clear up the prompt for parameter.
You can use Table Variables in SSRS dataset query like in my code where I am adding needed "empty" records for keep group footer in fixed postion (sample use pubs database):
DECLARE #NumberOfLines INT
DECLARE #RowsToProcess INT
DECLARE #CurrentRow INT
DECLARE #CurRow INT
DECLARE #cntMax INT
DECLARE #NumberOfRecords INT
DECLARE #SelectedType char(12)
DECLARE #varTable TABLE (# int, type char(12), ord int)
DECLARE #table1 TABLE (type char(12), title varchar(80), ord int )
DECLARE #table2 TABLE (type char(12), title varchar(80), ord int )
INSERT INTO #varTable
SELECT count(type) as '#', type, count(type) FROM titles GROUP BY type ORDER BY type
SELECT #cntMax = max(#) from #varTable
INSERT into #table1 (type, title, ord) SELECT type, N'', 1 FROM titles
INSERT into #table2 (type, title, ord) SELECT type, title, 1 FROM titles
SET #CurrentRow = 0
SET #SelectedType = N''
SET #NumberOfLines = #RowsPerPage
SELECT #RowsToProcess = COUNT(*) from #varTable
WHILE #CurrentRow < #RowsToProcess
BEGIN
SET #CurrentRow = #CurrentRow + 1
SELECT TOP 1 #NumberOfRecords = ord, #SelectedType = type
FROM #varTable WHERE type > #SelectedType
SET #CurRow = 0
WHILE #CurRow < (#NumberOfLines - #NumberOfRecords % #NumberOfLines) % #NumberOfLines
BEGIN
SET #CurRow = #CurRow + 1
INSERT into #table2 (type, title, ord)
SELECT type, '' , 2
FROM #varTable WHERE type = #SelectedType
END
END
SELECT type, title FROM #table2 ORDER BY type ASC, ord ASC, title ASC
Why can't you just UNION the two resultsets?
How about using a table valued function rather than a stored proc?
It's possible, only declare your table with '##'. Example:
DECLARE ##results TABLE (Number int
,Name nvarchar(250)
,Total1 money
,Total2 money
)
insert into ##results (Number, Name, Total1)
select number, name, sum(total)
from table1
group by number, name
update ##results
set total2 = total
from
(select number, sum(total) from table2) s
where s.number = number
select * from ##results

Resources