SQL Server 2008 CTE And CONTAINSTABLE Statement - Why the error? - sql

I am testing out moving our database from SQL Server 2005 to 2008. We use CTE's for paging.
When using full-text CONTAINSTABLE, the CTE will not run and generates an error.
Here's my non-working code-
WITH results AS (
SELECT ROW_NUMBER() over (ORDER BY GBU.CreateDate DESC ) as rowNum,
GBU.UserID,
NULL AS DistanceInMiles
FROM User GBU WITH (NOLOCK)
WHERE 1=1
AND GBU.CountryCode IN (SELECT [Value] FROM fn_Split('USA',','))
AND GBU.UserID IN (SELECT [KEY] FROM CONTAINSTABLE(VW_GBU_Search, *, 'COMPASS'))
)
SELECT * from results
WHERE rowNum BETWEEN 0 and 25
If I comment out the CONTAINSTABLE line, the statement executes. If I only run the SELECT statement (not the WITH), the statement executes fine.
The un-helpful error I get on this is:
Msg 0, Level 11, State 0, Line 0 A
severe error occurred on the current
command. The results, if any, should
be discarded. Msg 0, Level 20, State
0, Line 0 A severe error occurred on
the current command. The results, if
any, should be discarded.
Any suggestions?

Appears to be a bug. See http://connect.microsoft.com/SQLServer/feedback/ViewFeedback.aspx?FeedbackID=426981
Sounds like the fix should be in the next MSSQL SP.

Assuming the other answers are correct, and that the underlying issue is a bug, since you aren't referencing RANK from CONTAINSTABLE, perhaps a query something like the following would be a workaround, where "ID" is the ID column in VW_GBU_Search (untested)?
;WITH results AS (
SELECT ROW_NUMBER() OVER (ORDER BY GBU.CreateDate DESC ) AS rowNum,
GBU.UserID,
NULL AS DistanceInMiles
FROM User GBU WITH (NOLOCK)
WHERE 1=1
AND GBU.CountryCode IN (SELECT [Value] FROM fn_Split('USA',','))
AND GBU.UserID IN (SELECT ID FROM VW_GBU_Search WHERE CONTAINS(*, 'COMPASS'))
)
SELECT * FROM results
WHERE rowNum BETWEEN 0 AND 25
Also, why do you have the "1=1" clause? Can you eliminate it?

I banged my head against the wall on this problem for hours; here is a workaround:
ASSUME: A table in database called
Items ( ItemId int PK, Content varchar(MAX) ),
which has a fulltext index already applied.
GO
CREATE FUNCTION udf_SearchItemsTable(#FreeText)
RETURNS #SearchHits
TABLE(
Relevance int,
ItemId int,
Content varchar(MAX)
)
AS
BEGIN
INSERT #SearchHits
SELECT Results.[Rank] AS Relevance
,Items.ItemId AS ItemId
,Items.Content AS Content
FROM SearchableItems AS Items INNER JOIN
CONTAINSTABLE(SearchableItems, *, #FreeText) AS Results
Results.[Key] = Items.Id
RETURN
END
GO
...
GO
CREATE FUNCTION udf_SearchItems( #SearchText, #StartRowNum, #MaxRows)
RETURNS #SortedItems
TABLE (
ItemId int,
Content varchar(MAX)
)
AS
BEGIN
WITH Matches AS
(
SELECT
ROW_NUMBER() OVER (ORDER BY Hits.Relevance DESC) AS RowNum
,Hits.*
FROM ( udf_SearchItemsTable(#SearchText) ) AS Hits
)
SELECT
ItemId, Content
FROM
Matches
WHERE
Matches.RowNum BETWEEN #StartRowNum
AND #StartRowNum + #MaxRows
;
RETURN
END
GO
select * from udf_SearchItems('some free text stuff', 10, 20)

Related

How to use a special while loop in tsql, do while numeric

I'm loading some quite nasty data through Azure data factory
This is how the data looks after being loaded, existing of 2 parts:
1. Metadata of a test
2. Actual measurements of the test -> the measurement is numeric
Image I have about 10 times such 'packages' of 1.Metadata + 2.Measurements
What I would like it to be / what I'm looking for is the following:
The number column with 1,2,.... is what I'm looking for!
Imagine my screenshot could go no further but this goes along until id=10
I guess a while loop is necessary here...
Query before:
SELECT Field1 FROM Input
Query after:
SELECT GeneratedId, Field1 FROM Input
Thanks a lot in advance!
EDIT: added a hint:
Here is a solution, this requires SQL-SERVER 2012 or later.
Start by getting an Id column on your data. If you can do this previous to the script that would be even better, but if not, try something like this...
CREATE TABLE #InputTable (
Id INT IDENTITY(1, 1),
TestData NVARCHAR(MAX) )
INSERT INTO #InputTable (TestData)
SELECT Field1 FROM Input
Now create a query to get the GeneratedId of each package as well as the Id where they start and end. You can do this by getting all the records LIKE 'title%' since that is the first record of each package, then using ROW_NUMBER, Id, and LEAD for the GeneratedId, StartId, and EndId respectively.
SELECT
GeneratedId = ROW_NUMBER() OVER(ORDER BY (Id)),
StartId = Id,
EndId = LEAD(Id) OVER (ORDER BY (Id))
FROM #InputTable
WHERE TestData LIKE 'title%'
Lastly, join this to the input in order to get all the records, with the correct GeneratedId.
SELECT
package.GeneratedId, i.TestData
FROM (
SELECT
GeneratedId = ROW_NUMBER() OVER(ORDER BY (Id)),
StartId = Id,
EndId = LEAD(Id) OVER (ORDER BY (Id))
FROM #InputTable
WHERE TestData LIKE 'title%' ) package
INNER JOIN #InputTable i
ON i.Id >= package.StartId
AND (package.EndId IS NULL OR i.Id < package.EndId)

CTE Causing Conversion Failed when converting date and/or time from character string Error

I have created a very simple example showing an issue I have encountered on a much more complicated query using dynamic SQL and I can't figure out why the CTE is causing the error when the standard version is not.
Error when using the CTE:
Conversion failed when converting date and/or time from character string.
I am trying to achieve the following with my dynamic query:
Flag records that don't match a specified data type (date in this case). If they pass that step they are later checked against a look-up table to validate they are the values I was expecting (validate dates in a specified range).
-- Table Setup
CREATE TABLE #ValidDate
(
V INT IDENTITY (1, 1) NOT NULL,
VDate DATE NULL
)
INSERT INTO #ValidDate
VALUES ('02/20/2014'), ('02/21/2014'), ('02/22/2014'), ('02/23/2014'), ('02/25/2014')
CREATE TABLE #DatesToValidate
(
I INT IDENTITY (1, 1) NOT NULL,
IDate VARCHAR(30) NULL
)
INSERT INTO #DatesToValidate
VALUES ('apple'), ('02/21/2014'), ('orange'), ('02/23/2014')
CREATE TABLE #Errors
(
ID INT,
Dates VARCHAR(30)
)
INSERT INTO #Errors
SELECT *
FROM #DatesToValidate
WHERE ISDATE(IDate) <> 1
Below is the standard Query (Works):
SELECT *
FROM #DatesToValidate
WHERE I NOT IN ( SELECT ID
FROM #Errors)
AND IDate NOT IN ( SELECT VDate
FROM #ValidDate)
Below is the CTE (Does not Work):
;WITH CTE AS
(
SELECT *
FROM #DatesToValidate
WHERE I NOT IN ( SELECT ID
FROM #Errors)
)
SELECT *
FROM CTE
WHERE IDate NOT IN ( SELECT VDate
FROM #ValidDate)
This SQL Fiddle shows the problem. Neither version is guaranteed to work. If you swap the order of the conditions in the and then you'll get a conversion failure.
The reason the and version happens to work in this case is because of short-circuiting. When the first condition fails, the second doesn't get run. This is an optimization that SQL Server can take advantage of. And does in this case, so you think it works.
The CTE version doesn't work because there is no guarantee on the ordering of the operations. And SQL Server decides to do something different. Namely, it tries to evaluate 'apple' as a date.
The right solution for both is to cast before the compare. Because the cast may not work, you need to be careful. You should use case in SQL Server for this purpose; it is the only construct that guarantees sequential evaluation.
So, try this:
;WITH CTE AS
(
SELECT *
FROM DatesToValidate
WHERE I NOT IN ( SELECT ID
FROM Errors)
)
SELECT *
FROM CTE
WHERE (case when isdate(IDate) = 1 then cast(IDate as date)
end) NOT IN ( SELECT VDate
FROM ValidDate)
This particular part is failing cause VDate is a date type column and IDate is varchar. So you need to cast VDate like cast(VDate as varchar(30) as shown below.
See a demo fiddle here http://sqlfiddle.com/#!3/abfc0/7
WHERE IDate NOT IN (SELECT VDate FROM ValidDate) <--- Failing
Try your sql this way
;WITH CTE AS
(
SELECT *
FROM DatesToValidate
WHERE I NOT IN ( SELECT ID
FROM Errors)
)
SELECT *
FROM CTE
WHERE IDate NOT IN (SELECT cast(VDate as varchar(30)) <--- Cast Here
FROM ValidDate)

SELECT() next row in value assigning statement?

I'm trying to select rows one by one in a while loop in SQL Server. And I'm using the only way I know to select the next row using ROW_NUMBER function. But I also have to assign values to t-sql variables in the same SELECT statement thus I'm getting the following error;
A SELECT statement that assigns a value to a variable must not
be combined with data-retrieval operations.
Anyway my code is like this;
WHILE (#i < 5)
BEGIN
SELECT -- This is where the error occurs
#resultId = video.id, -- this is the value assigning i need to do
ROW_NUMBER() OVER (ORDER BY dateAdded DESC) AS ROWID
FROM videoTest.dbo.video
LEFT JOIN videoTest.dbo.aspnet_Users
ON video.userId=aspnet_Users.UserId
WHERE aspnet_Users.UserName=#searchUserName AND ROWID = #i
-- Processing #resultId
SELECT
compilationId
FROM videoTest.dbo.comp
WHERE vidId = #resultId -- i need the id from above
....
....
.....
......
#i = #i + 1
END
I would be much appreciated if you can show me way to select next in an alternative way without getting this error, or a way to solve this error in my current code.
Thanks.
You need to separate out the selection of the data from the retrieval of that data into a variable. You could use e.g. a CTE (Common Table Expression) to set up the data, and then operate on that data. However, on a more basic level : why are you assigning the id to #resultId five times?? You don't seem to be doing anything with the #resultId in the meantime.....
Could you change your logic to be more set-based ? Instead of a WHILE construct - just select the appropriate values from the CTE:
;WITH VideoData AS
(
SELECT
video.id,
ROW_NUMBER() OVER (ORDER BY dateAdded DESC) AS ROWID
FROM
videoTest.dbo.video
LEFT JOIN
videoTest.dbo.aspnet_Users ON video.userId = aspnet_Users.UserId
WHERE
aspnet_Users.UserName = #searchUserName
)
SELECT id, ROWID
FROM VideoData
WHERE ROWID <= 5
Update: if you need the five video.id values for later processing, try something like this:
DECLARE #VideoIDs TABLE (VideoID INT)
;WITH VideoData AS
(
... (same as before)
)
INSERT INTO #VideoIDs(VideoID)
SELECT id
FROM VideoData
WHERE ROWID <= 5
-- use your values in the table variable here.....
I see that you need video.id. Why not just order by dateAdded, and store it in a cursor:
SELECT
INTO CURSOR #SomeCursor
video.id
FROM videoTest.dbo.video
LEFT JOIN videoTest.dbo.aspnet_Users
ON video.userId=aspnet_Users.UserId
WHERE aspnet_Users.UserName=#searchUserName
ORDER BY dateAdded DESC
and then iterate #SomeCursor?

Conversion failed when converting the varchar value to int

Microsoft SQL Server 2008 (SP1), getting an unexpected 'Conversion failed' error.
Not quite sure how to describe this problem, so below is a simple example. The CTE extracts the numeric portion of certain IDs using a search condition to ensure a numeric portion actually exists. The CTE is then used to find the lowest unused sequence number (kind of):
CREATE TABLE IDs (ID CHAR(3) NOT NULL UNIQUE);
INSERT INTO IDs (ID) VALUES ('A01'), ('A02'), ('A04'), ('ERR');
WITH ValidIDs (ID, seq)
AS
(
SELECT ID, CAST(RIGHT(ID, 2) AS INTEGER)
FROM IDs
WHERE ID LIKE 'A[0-9][0-9]'
)
SELECT MIN(V1.seq) + 1 AS next_seq
FROM ValidIDs AS V1
WHERE NOT EXISTS (
SELECT *
FROM ValidIDs AS V2
WHERE V2.seq = V1.seq + 1
);
The error is, 'Conversion failed when converting the varchar value 'RR' to data type int.'
I can't understand why the value ID = 'ERR' should be being considered for conversion because the predicate ID LIKE 'A[0-9][0-9]' should have removed the invalid row from the resultset.
When the base table is substituted with an equivalent CTE the problem goes away i.e.
WITH IDs (ID)
AS
(
SELECT 'A01'
UNION ALL
SELECT 'A02'
UNION ALL
SELECT 'A04'
UNION ALL
SELECT 'ERR'
),
ValidIDs (ID, seq)
AS
(
SELECT ID, CAST(RIGHT(ID, 2) AS INTEGER)
FROM IDs
WHERE ID LIKE 'A[0-9][0-9]'
)
SELECT MIN(V1.seq) + 1 AS next_seq
FROM ValidIDs AS V1
WHERE NOT EXISTS (
SELECT *
FROM ValidIDs AS V2
WHERE V2.seq = V1.seq + 1
);
Why would a base table cause this error? Is this a known issue?
UPDATE #sgmoore: no, doing the filtering in one CTE and the casting in another CTE still results in the same error e.g.
WITH FilteredIDs (ID)
AS
(
SELECT ID
FROM IDs
WHERE ID LIKE 'A[0-9][0-9]'
),
ValidIDs (ID, seq)
AS
(
SELECT ID, CAST(RIGHT(ID, 2) AS INTEGER)
FROM FilteredIDs
)
SELECT MIN(V1.seq) + 1 AS next_seq
FROM ValidIDs AS V1
WHERE NOT EXISTS (
SELECT *
FROM ValidIDs AS V2
WHERE V2.seq = V1.seq + 1
);
It's a bug and has already been reported as SQL Server should not raise illogical errors (as I said, it's hard to describe this one!) by Erland Sommarskog.
The response from the SQL Server Programmability Team is, "the issue is that SQL Server raises errors [too] eagerly due to pushing of prediates/expressions during query execution without considering the logical result of the query."
I've now voted for a fix, everyone do the same please :)
What if you replace the section
SELECT ID, CAST(RIGHT(ID, 2) AS INTEGER)
FROM IDs
WHERE ID LIKE 'A[0-9][0-9]'
With
SELECT ID, CAST(RIGHT(ID, 2) AS INTEGER)
FROM
(
select ID from IDs
WHERE ID LIKE 'A[0-9][0-9]'
)
This happened to me because I did a Union and was not careful to make sure both queries had their fields in the same order. Once I fixed that, it was fine.

Make SQL Select same row multiple times

I need to test my mail server. How can I make a Select statement
that selects say ID=5469 a thousand times.
If I get your meaning then a very simple way is to cross join on a derived query on a table with more than 1000 rows in it and put a top 1000 on that. This would duplicate your results 1000 times.
EDIT: As an example (This is MSSQL, I don't know if Access is much different)
SELECT
MyTable.*
FROM
MyTable
CROSS JOIN
(
SELECT TOP 1000
*
FROM
sysobjects
) [BigTable]
WHERE
MyTable.ID = 1234
You can use the UNION ALL statement.
Try something like:
SELECT * FROM tablename WHERE ID = 5469
UNION ALL
SELECT * FROM tablename WHERE ID = 5469
You'd have to repeat the SELECT statement a bunch of times but you could write a bit of VB code in Access to create a dynamic SQL statement and then execute it. Not pretty but it should work.
Create a helper table for this purpose:
JUST_NUMBER(NUM INT primary key)
Insert (with the help of some (VB) script) numbers from 1 to N. Then execute this unjoined query:
SELECT MYTABLE.*
FROM MYTABLE,
JUST_NUMBER
WHERE MYTABLE.ID = 5469
AND JUST_NUMBER.NUM <= 1000
Here's a way of using a recursive common table expression to generate some empty rows, then to cross join them back onto your desired row:
declare #myData table (val int) ;
insert #myData values (666),(888),(777) --some dummy data
;with cte as
(
select 100 as a
union all
select a-1 from cte where a>0
--generate 100 rows, the max recursion depth
)
,someRows as
(
select top 1000 0 a from cte,cte x1,cte x2
--xjoin the hundred rows a few times
--to generate 1030301 rows, then select top n rows
)
select m.* from #myData m,someRows where m.val=666
substitute #myData for your real table, and alter the final predicate to suit.
easy way...
This exists only one row into the DB
sku = 52 , description = Skullcandy Inkd Green ,price = 50,00
Try to relate another table in which has no constraint key to the main table
Original Query
SELECT Prod_SKU , Prod_Descr , Prod_Price FROM dbo.TB_Prod WHERE Prod_SKU = N'52'
The Functional Query ...adding a not related table called 'dbo.TB_Labels'
SELECT TOP ('times') Prod_SKU , Prod_Descr , Prod_Price FROM dbo.TB_Prod,dbo.TB_Labels WHERE Prod_SKU = N'52'
In postgres there is a nice function called generate_series. So in postgreSQL it is as simple as:
select information from test_table, generate_series(1, 1000) where id = 5469
In this way, the query is executed 1000 times.
Example for postgreSQL:
CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; --To be able to use function uuid_generate_v4()
--Create a test table
create table test_table (
id serial not null,
uid UUID NOT NULL,
CONSTRAINT uid_pk PRIMARY KEY(id));
-- Insert 10000 rows
insert into test_table (uid)
select uuid_generate_v4() from generate_series(1, 10000);
-- Read the data from id=5469 one thousand times
select id, uid, uuid_generate_v4() from test_table, generate_series(1, 1000) where id = 5469;
As you can see in the result below, the data from uid is read 1000 times as confirmed by the generation of a new uuid at every new row.
id |uid |uuid_generate_v4
----------------------------------------------------------------------------------------
5469|"10791df5-ab72-43b6-b0a5-6b128518e5ee"|"5630cd0d-ee47-4d92-9ee3-b373ec04756f"
5469|"10791df5-ab72-43b6-b0a5-6b128518e5ee"|"ed44b9cb-c57f-4a5b-ac9a-55bd57459c02"
5469|"10791df5-ab72-43b6-b0a5-6b128518e5ee"|"3428b3e3-3bb2-4e41-b2ca-baa3243024d9"
5469|"10791df5-ab72-43b6-b0a5-6b128518e5ee"|"7c8faf33-b30c-4bfa-96c8-1313a4f6ce7c"
5469|"10791df5-ab72-43b6-b0a5-6b128518e5ee"|"b589fd8a-fec2-4971-95e1-283a31443d73"
5469|"10791df5-ab72-43b6-b0a5-6b128518e5ee"|"8b9ab121-caa4-4015-83f5-0c2911a58640"
5469|"10791df5-ab72-43b6-b0a5-6b128518e5ee"|"7ef63128-b17c-4188-8056-c99035e16c11"
5469|"10791df5-ab72-43b6-b0a5-6b128518e5ee"|"5bdc7425-e14c-4c85-a25e-d99b27ae8b9f"
5469|"10791df5-ab72-43b6-b0a5-6b128518e5ee"|"9bbd260b-8b83-4fa5-9104-6fc3495f68f3"
5469|"10791df5-ab72-43b6-b0a5-6b128518e5ee"|"c1f759e1-c673-41ef-b009-51fed587353c"
5469|"10791df5-ab72-43b6-b0a5-6b128518e5ee"|"4a70bf2b-ddf5-4c42-9789-5e48e2aec441"
Of course other DBs won't necessarily have the same function but it could be done:
See here.
If your are doing this in sql Server
declare #cnt int
set #cnt = 0
while #cnt < 1000
begin
select '12345'
set #cnt = #cnt + 1
end
select '12345' can be any expression
Repeat rows based on column value of TestTable. First run the Create table and insert statement, then run the following query for the desired result.
This may be another solution:
CREATE TABLE TestTable
(
ID INT IDENTITY(1,1),
Col1 varchar(10),
Repeats INT
)
INSERT INTO TESTTABLE
VALUES ('A',2), ('B',4),('C',1),('D',0)
WITH x AS
(
SELECT TOP (SELECT MAX(Repeats)+1 FROM TestTable) rn = ROW_NUMBER()
OVER (ORDER BY [object_id])
FROM sys.all_columns
ORDER BY [object_id]
)
SELECT * FROM x
CROSS JOIN TestTable AS d
WHERE x.rn <= d.Repeats
ORDER BY Col1;
This trick helped me in my requirement.
here, PRODUCTDETAILS is my Datatable
and orderid is my column.
declare #Req_Rows int = 12
;WITH cte AS
(
SELECT 1 AS Number
UNION ALL
SELECT Number + 1 FROM cte WHERE Number < #Req_Rows
)
SELECT PRODUCTDETAILS.*
FROM cte, PRODUCTDETAILS
WHERE PRODUCTDETAILS.orderid = 3
create table #tmp1 (id int, fld varchar(max))
insert into #tmp1 (id, fld)
values (1,'hello!'),(2,'world'),(3,'nice day!')
select * from #tmp1
go
select * from #tmp1 where id=3
go 1000
drop table #tmp1
in sql server try:
print 'wow'
go 5
output:
Beginning execution loop
wow
wow
wow
wow
wow
Batch execution completed 5 times.
The easy way is to create a table with 1000 rows. Let's call it BigTable. Then you would query for the data you want and join it with the big table, like this:
SELECT MyTable.*
FROM MyTable, BigTable
WHERE MyTable.ID = 5469