Bad query plan for self-referencing CTE view query and variable in WHERE clause. Is there way out or this is SQL Server defect?

Please help. Thank you for your time and expertise.
Prerequisites: sql query needs to be a view. Real view is more than recursion. It computes location path,  is used in JOINs and returns this path.
Problem: no matter what I tried, sql server does not produce 'index seek' when using variable but does with literal.
See full reproduction code below.
I expect that query SELECT lcCode FROM dbo.vwLocationCodes l WHERE l.lcID = @lcID will seek UNIQUE index but it does not.
I tried these:
1. Changing UX and/or PK to be CLUSTERED.
2. query OPTION(RECOMPILE)
3. FORCESEEK on view
4. SQL Server 2012/2014
5. Wrap it into function and CROSS APPLY. On large outer number of rows this just dies, no solution
but to no avail. This smells like a bug in SQL Server. I am seeking your confirmation.
I am thinking it is a bug as variable value is high-cardinality, 1, and query is against unique key. This must produce single seek, depending if clustered or nonclustred index is unique
Thanks
Vladimir
use tempdb
BEGIN TRAN
-- setup definition
CREATE TABLE dbo.LocationHierarchy(
lcID int NOT NULL ,
lcHID hierarchyid NOT NULL,
lcCode nvarchar(25) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
lcHIDParent AS lcHID.GetAncestor(1) PERSISTED,
CONSTRAINT PK_LocationHierarchy_lcID PRIMARY KEY NONCLUSTERED (lcID ASC),
CONSTRAINT UX_LocationHierarchy_pltID_lcHID UNIQUE CLUSTERED (lcHID ASC)
-- add some data
INSERT INTO dbo.LocationHierarchy
VALUES
(1, '/', 'A')
,(2, '/1/', 'B')
,(3, '/1/1/', 'C')
,(4, '/1/1/1/', 'D')
--DROP VIEW dbo.vwLocationCodes
GO
CREATE VIEW dbo.vwLocationCodes
AS
WITH ru AS
SELECT
lh.lcID
,lh.lcCode
,lh.lcHID
,CAST('/' + lh.lcCode + '/' as varchar(8000)) as LocationPath
-- to support recursion
,lh.lcHIDParent
FROM dbo.LocationHierarchy lh
UNION ALL
SELECT
ru.lcID
,ru.lcCode
,ru.lcHID
,CAST('/' + lh.lcCode + ru.LocationPath as varchar(8000)) as LocationPath
,lh.lcHIDParent
FROM dbo.LocationHierarchy lh
JOIN ru ON ru.lcHIDParent = lh.lcHID
SELECT
lh.lcID
,lh.lcCode
,lh.LocationPath
,lh.lcHID
FROM ru lh
WHERE lh.lcHIDParent IS NULL
GO
-- get data via view
SELECT
CONCAT(SPACE(l.lcHID.GetLevel() * 4), lcCode) as LocationIndented
FROM dbo.vwLocationCodes l
ORDER BY lcHID
GO
SET SHOWPLAN_XML ON
GO
DECLARE @lcID int = 2
-- I believe this produces bad plan and is defect in SQL Server optimizer.
-- variable value cardinality is 1 and SQL Server should know that. Optiomal plan is to do index seek with key lookup.
-- This does not happen.
SELECT lcCode FROM dbo.vwLocationCodes l WHERE l.lcID = @lcID -- bad plan
-- this is a plan I expect.
SELECT lcCode FROM dbo.vwLocationCodes l WHERE l.lcID = 2 -- good plan
-- I reviewed these but I need a view here, can't be SP
-- http://sqlblogcasts.com/blogs/tonyrogerson/archive/2008/05/17/non-recursive-common-table-expressions-performance-sucks-1-cte-self-join-cte-sub-query-inline-expansion.aspx
-- http://social.msdn.microsoft.com/Forums/sqlserver/en-US/22d2d580-0ff8-4a9b-b0d0-e6a8345062df/issue-with-select-using-a-recursive-cte-and-parameterizing-the-query?forum=transactsql
GO
SET SHOWPLAN_XML OFF
GO
ROLLBACK
Vladimir Moldovanenko

Here is more... note that I am creating table Items and these can be in Locations.
I am trying LEFT JOIN and OUTER APLLY to 'bend' query into NESTED LOOP and SEEK. There has to be nested loop, 2 rows against 4. But SQL Server fails to generate optimal plan with SEEK. Even RECOMPILE does not help
use tempdb
BEGIN TRAN
-- setup definition
CREATE TABLE dbo.LocationHierarchy(
lcID int NOT NULL ,
lcHID hierarchyid NOT NULL,
lcCode nvarchar(25) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
lcHIDParent AS lcHID.GetAncestor(1) PERSISTED,
CONSTRAINT PK_LocationHierarchy_lcID PRIMARY KEY NONCLUSTERED (lcID ASC),
CONSTRAINT UX_LocationHierarchy_pltID_lcHID UNIQUE CLUSTERED (lcHID ASC)
-- add some data
INSERT INTO dbo.LocationHierarchy
VALUES
(1, '/', 'A')
,(2, '/1/', 'B')
,(3, '/1/1/', 'C')
,(4, '/1/1/1/', 'D')
--DROP VIEW dbo.vwLocationCodes
GO
--DECLARE @Count int = 10;
--WITH L0 AS (SELECT N FROM (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N (N))-- 10 rows
--,L1 AS (SELECT n1.N FROM L0 n1 CROSS JOIN L0 n2) -- 100 rows
--,L2 AS (SELECT n1.N FROM L1 n1 CROSS JOIN L1 n2) -- 10,000 rows
--,L3 AS (SELECT n1.N FROM L2 n1 CROSS JOIN L2 n2) -- 100,000,000 rows
--,x AS
-- SELECT TOP (ISNULL(@Count, 0))
-- ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as Number
-- FROM L3 n1
--SELECT Number as itmID, NTILE(4)OVER(ORDER BY Number) as lcID
--INTO dbo.Items
--FROM x
----ORDER BY n1.N
--ALTER TABLE dbo.Items ALTER COLUMN itmID INT NOT NULL
--ALTER TABLE dbo.Items ADD CONSTRAINT PK PRIMARY KEY CLUSTERED (itmID)
CREATE TABLE dbo.Items (itmID int NOT NULL PRIMARY KEY, lcID int NOT NULL)
INSERT INTO dbo.items
VALUES(1, 1)
,(2, 3)
GO
CREATE VIEW dbo.vwLocationCodes
AS
WITH ru AS
SELECT
lh.lcID
,lh.lcCode
,lh.lcHID
,CAST('/' + lh.lcCode + '/' as varchar(8000)) as LocationPath
-- to support recursion
,lh.lcHIDParent
FROM dbo.LocationHierarchy lh
UNION ALL
SELECT
ru.lcID
,ru.lcCode
,ru.lcHID
,CAST('/' + lh.lcCode + ru.LocationPath as varchar(8000)) as LocationPath
,lh.lcHIDParent
FROM dbo.LocationHierarchy lh
JOIN ru ON ru.lcHIDParent = lh.lcHID
SELECT
lh.lcID
,lh.lcCode
,lh.LocationPath
,lh.lcHID
FROM ru lh
WHERE lh.lcHIDParent IS NULL
GO
-- get data via view
SELECT
CONCAT(SPACE(l.lcHID.GetLevel() * 4), lcCode) as LocationIndented
FROM dbo.vwLocationCodes l
ORDER BY lcHID
GO
--SET SHOWPLAN_XML ON
GO
DECLARE @lcID int = 2
-- I believe this produces bad plan and is defect in SQL Server optimizer.
-- variable value cardinality is 1 and SQL Server should know that. Optiomal plan is to do index seek with key lookup.
-- This does not happen.
SELECT lcCode FROM dbo.vwLocationCodes l WHERE l.lcID = @lcID-- OPTION(RECOMPILE) -- bad plan
-- this is a plan I expect.
SELECT lcCode FROM dbo.vwLocationCodes l WHERE l.lcID = 2 -- good plan
SELECT *
FROM dbo.Items itm
LEFT JOIN dbo.vwLocationCodes l ON l.lcID = itm.lcID
OPTION(RECOMPILE)
SELECT *
FROM dbo.Items itm
OUTER APPLY
SELECT *
FROM dbo.vwLocationCodes l
WHERE l.lcID = itm.lcID
) l
-- I reviewed these but I need a view here, can't be SP
-- http://sqlblogcasts.com/blogs/tonyrogerson/archive/2008/05/17/non-recursive-common-table-expressions-performance-sucks-1-cte-self-join-cte-sub-query-inline-expansion.aspx
-- http://social.msdn.microsoft.com/Forums/sqlserver/en-US/22d2d580-0ff8-4a9b-b0d0-e6a8345062df/issue-with-select-using-a-recursive-cte-and-parameterizing-the-query?forum=transactsql
GO
--SET SHOWPLAN_XML OFF
GO
ROLLBACK
Vladimir Moldovanenko

Similar Messages

  • Hi, i am a littl confused as I logged into creative cloud and bough the in design plan for a year but i cant seem to donwload it... there is a window that pops up and it says its downloading but its taking forever? any advice?

    hi, i am a littl confused as I logged into creative cloud and bough the in design plan for a year but i cant seem to donwload it... there is a window that pops up and it says its downloading but its taking forever? any advice?

    Hi Dima,
    Please refer to the help documents below:
    Troubleshoot Creative Cloud download and install issues
    Error downloading, installing, or updating Creative Cloud applications
    Regards,
    Sheena

  • The query does not reference any table when attempting to build the WHERE clause.  (IES 00022)

    Hi
    I am getting below error.
    The query does not reference any table when attempting to build the WHERE clause.  (IES 00022)
    This error is in Validating Measue object in IDT.
    It is not throwing error for dimensions objects but only for measure objects.
    My BO version is 4.1
    Backend is Teradata 14.1.
    Regards
    Gaurav.

    Hi
    In the dimension/Measure definition, you can select the table. Find the below screenshot.
    If you still getting the issue…Can you please share your screenshot, for better understanding the issue?

  • My 4th generation ipod touch is not turning off when i hold the wake button for few seconds the Voice Control Function Starts Automatically.Please help me to get out of this problem.

    My 4th generation ipod touch is not turning off when i hold the wake button for few seconds the Voice Control Function Starts Automatically.Please help me to get out of this problem.

    Sorry i mistakenly made this question. It was alright. No Problem At ALL

  • Different query plans for same query on same DB

    Hi,
    HP-Ux
    Oracle Database 10.2.0.4
    We are experiencing a strange issue. One of our night batch process is taking invariably more time to execute. The process does not consume time at 1 particular query. Everyday we find a new query taking more time than previous execution.
    Now, when we see the explain plan while the query is executing, we see NESTED LOOP SEMI (with improper index being used). At the same time if we take the query and see the explain plan seperately, we get HASH JOIN SEMI (with proper index being used). Also, if we execute this query with the values as in procedure, it finishes within mili seconds (as it should).
    The tables and indexes are analyzed everyday before the process starts.
    Can anybody explain, why the same query shows two different plans at the same time ?
    Thanks a lot in advance :)

    Aalap Sharma wrote:
    HP-Ux
    Oracle Database 10.2.0.4
    We are experiencing a strange issue. One of our night batch process is taking invariably more time to execute. The process does not consume time at 1 particular query. Everyday we find a new query taking more time than previous execution.
    Now, when we see the explain plan while the query is executing, we see NESTED LOOP SEMI (with improper index being used). At the same time if we take the query and see the explain plan seperately, we get HASH JOIN SEMI (with proper index being used). Also, if we execute this query with the values as in procedure, it finishes within mili seconds (as it should).
    The tables and indexes are analyzed everyday before the process starts.
    Can anybody explain, why the same query shows two different plans at the same time ?As already mentioned, you might hit typical issues in 10.2: The column workload based SIZE AUTO statistics gathering feature and/or bind variable peeking.
    How do you analyze the tables and indexes before the process starts? Can you share the exact call with parameters?
    Some ideas:
    1. If your process is "new", then the column workload monitoring of the database might recognize the column usage pattern and generate histograms on some of your columns. It might take a while until the workload has been established so that all columns got histograms according to the workload (It needs a certain number of usages/executions before the workload is registered as relevant). Until then you might get different execution plans each time the statistics are refreshed due to new histograms being added.
    2. If the default 10g statistics gathering job is active, it might gather different statistics during the night than your individual job that runs prior to the processing. This could be one possible explanation why you get different plans on the next day.
    3. "Bind Variable Peeking" is possibly another issue you might run into. How do you test the query so that you get a different, well performing plan? Does your original statement use bind variables? Do you use literals to reproduce? Note that using EXPLAIN PLAN on statements involving bind variables can lie, since it doesn't perform bind variable peeking by default.
    Regards,
    Randolf
    Oracle related stuff blog:
    http://oracle-randolf.blogspot.com/
    SQLTools++ for Oracle (Open source Oracle GUI for Windows):
    http://www.sqltools-plusplus.org:7676/
    http://sourceforge.net/projects/sqlt-pp/

  • Bad explain plan for count(*)

    select count(*) from foo
    where flda = value and
    fldba like 'text'
    and contains(col,'some text') > 0
    takes long time - does not use domain index.
    If I replace count(*) w/*, runs like a champ, and uses domain index.
    null

    Define a "good" or a "bad" execution plan.
    See Wolfgang Breitling's Tuning by Cardinality Feedback:
    http://www.centrexcc.com/Tuning%20by%20Cardinality%20Feedback.pdf
    OBSERVATION
    IF AN ACCESS PLAN IS NOT OPTIMAL IT IS BECAUSE THE CARDINALITY ESTIMATE FOR ONE OR MORE OF
    THE ROW SOURCES IS GROSSLY INCORRECT."
    CONJECTURE
    THE CBO DOES AN EXCELLENT JOB OF FINDING THE BEST ACCESS PLAN FOR A GIVEN SQL PROVIDED
    IT IS ABLE TO ACCURATELY ESTIMATE THE CARDINALITIES OF THE ROW SOURCES IN THE PLANSo the flipside of that is that if you can identify executions plans where the estimates were not accurate (see DBMS_XPLAN.DISPLAY_CURSOR) then it is more llikely that a suboptimal plan was used and more optimal plans are possible.
    Edited by: Dom Brooks on Aug 21, 2012 1:06 PM
    link /text missing for some reason

  • Reading the DAX query plan of a trace against a query on a Tabular model

    Hi,
    I'm monitoring a Tabular model queried by an Excel workbook. Inside SQL Profiler I've choosen as events the VertiPaq SE Query End, the Query End and the DAX Query Plan. But this last event isn't more readable.
    How can I read the info collected in the DAX Query Plan in a simple manner?
    Thanks

    It is fairly complex to read the DAX Query Plan. I don't think there are currently tools that make it easier to read. (Watch the DAX Studio project on codeplex as they may include tools for this in the future.) I would recommend you review the Tabular
    Performance Guide which has a good explanation of the DAX Query Plan:
    http://aka.ms/ASTabPerf2012
    http://artisconsulting.com/Blogs/GregGalloway

  • Download Links for JMS Explorer to view message and its details

    Can i get the links for downloading the JMS explorer to view messages and its details on j2ee server.

    I checked Hermesjms tool but found that it does not work with j2ee server.
    Is there any other JMS explorer tool to view messages and its details on j2ee server specifically.

  • [Solved] Referencing another View Objects Bind Variables

    Hello,
    I have a Master view object and a detail object.
    The Master object, vProjects, has a bind variable :pProjectId that the user selects by using a selectOneChoice component. The View object vSections then shows only those Sections that have the selected ProjectId.
    I have a view link to get to the lower level which connects vSections to vRequirements.
    This works well except that the vRequirements lists all requirements that match the SecID I pass through the link. I need it to list only the ones that match the SecID AND the ProjectId chosen earlier.
    Is there a way to reference the bind variable that I used in vProjects inside of vRequirements?
    Thank you,
    Nelson
    Message was edited by:
    TheNelson

    Thanks, I can't believe I didn't think of that. I was rewriting queries and everything.
    Thanks again.

  • TSQL for capture and insert to a user table when blcoking is happening on a SQL server

    Hi,
    I am searching for a TSQL which will capture the blocking script to a table with execution plan and all other necessaryu details. I tried with below script but its not giving the text for blocked statement
    I t will be nice if i get RequestingText and  BlockingTest
    SELECT @@servername as Instancename,getdate() as [date],session_id,start_time,status,command,database_id,blocking_session_id,
    wait_type,wait_time,last_wait_type,ST.TEXT AS SQL,
    wait_resource,open_transaction_count,cpu_time,total_elapsed_time,reads,writes,logical_reads,text_size from sys.dm_exec_requests SR
    CROSS APPLY SYS.DM_EXEC_SQL_TEXT(SR.SQL_HANDLE) AS ST 
    where blocking_session_id >0
    I tried the below link also, this this script also got blocked
    http://blog.sqlauthority.com/2010/10/06/sql-server-quickest-way-to-identify-blocking-query-and-resolution-dirty-solution/

    But when you run select * from sys.sysprocesses where blocked >0 
    i can see blocking
    and it will be nice if I get RequestingText and BlockingTest
    in the user table
    You can get the requesting text for the spid that is blocked, plus all the other information you get from sys.sysprocesses by
    select SUBSTRING(st1.text, (p.stmt_start/2)+1,
    ((CASE p.stmt_end
    WHEN -1 THEN DATALENGTH(st1.text)
    ELSE p.stmt_end
    END - p.stmt_start)/2) + 1) AS RequestingText,
    p.*
    from sys.sysprocesses p
    CROSS APPLY sys.dm_exec_sql_text(p.sql_handle) AS st1
    where p.blocked <> 0;
    But you will not bee able to get the command that caused the lock that is causing the blocking.  SQL does not keep that information.  For a lock, it only keeps the spid that owns it, what is locked, and the kind of lock.  It does not keep
    the command that caused the lock.  And while you can, at least sometimes get the last command the blocking spid has issued, but that may well not be the command that caused the lock.  The command that caused the lock might be anything the blocking
    spid ran since it entered transaction state.  And since that could be the last command or many, many commands ago, you won't be able to get that information with a query into the system tables and/or views.
    Tom

  • Data Services Designer Query tranform dynamic where clause or another way

    I have an XML source that has 5 fields.  These fields are used to query 4 different SQL table data sources and do an AND inthe where clause of a query transform.  Works well if all 5 fields in the xml have data.  If one (or more) are blank, then it runs the where checking for blank matches and gives no results.  What I want to do is if a field is blank, do not use it in the where clause.  Could this be an outer join setup?  Issue is that if there is a PhoneNumber input, then to be considered a result, there must be a PhoneNumber match on potential result records...

    I have an XML source that has 5 fields.  These fields are used to query 4 different SQL table data sources and do an AND inthe where clause of a query transform.  Works well if all 5 fields in the xml have data.  If one (or more) are blank, then it runs the where checking for blank matches and gives no results.  What I want to do is if a field is blank, do not use it in the where clause.  Could this be an outer join setup?  Issue is that if there is a PhoneNumber input, then to be considered a result, there must be a PhoneNumber match on potential result records...

  • I need help entering ALL of my addresses for Christmas card address labels on my new iMac.  Whats the best way to do this?

    Anyone have any tips how to get started with address labels?  I went to my address book, but it looks so time intensive.  Can I mail merge addresses I put into a excell spread sheet to print labels?? 

    Don't waste your time with formatting in any of those programs - creating two pages for cards, blah blah blah.
    The best way to mail merge on a mac is using address book.
    People think merging got harder, but apple made it easier - more intuitive. You don't merge mailing labels through a word processor - you do it through the program which houses the addresses.  If the addresses aren't in address book yet, that's a snap.
    First, go to your program/file with the addresses (pages, numbers, excel, etc.). I think it's easiest to use numbers.
    make sure the column headings are set to Mac Address Book Standard Names - that way you won't have to spend too much time mapping them in address book. (if you're not sure what those are - open address book and just a view a card/record, or open pages or numbers and search in the help menu for the term "merge field names" and it will give you a list. They're pretty simple - things like "First Name" "Last Name" "Street Address" etc.
    For christmas cards, I add an extra column under the heading "Note" and input "Christmas Card List" for every record.
    Once your data is formatted with the correct column names, export the list to a csv file.
    Open Address book in mac and import the list - make sure the columns match up with the correct fields.
    It might ask if you want to overwrite or update - that's up to you.
    If you know your christmas card list has the most recent address info, hit update.
    Once the import is done it should take you to the front page for the address book in which you'll be given the choice to view Icloud addresses (if you sync those) and also the Last import. Any groups or smart groups you have will show up in the list too.
    Click on Last import - make sure all the addresses and names you just imported are there. If not, something went wrong - go back and check your import field mapping. If all's good, keep rollin'. (it helps to scroll to the bottom of the import list to get the count of imported records)
    Here, you can do one of two things - create a group or a smart group.
    For a regular group - Highlight all the records on the right panel in your last import, then go to file > new group from selection. Rename your group christmas cards or something like that.
    I prefer to create smart groups.
    For that, goto file > new smart group. You'll come to a filter window. Click on the first drop down box and scroll to "Note." Then in the second drop down box choose "is" and in the third and final input box on the right, enter the text from the note column you created in the address list file. I used "Christmas Card List".
    That will create a smart group, such that any time you enter a new address or contact in the address book with the note field that equals "christmas Card list" it will automatically be entered into this group.
    Either way, you're only one step away from labels.
    With that Christmas Card List selected in address book, go to print. When the print dialogue box opens, make sure to click on the button for show details toward the bottom left. That will open up your choices for style.
    Under "Style" click on mailing labels.
    Under "Layout" choose your label layout (i use avery 5160). It will show you a picture of the label and you can tell if it's the right one.  You'll also notice the labels are being merged correctly, with one address per label, rather than one name for the entire page - like if you try to do this through pages.
    Print away.
    Hope that helps.
    Craig

  • I'm looking for a weight wedding reminders to an email that I want to follow up in a few weeks. Is there way to do this?

    I just migrated to Apple and I'm having a little hard time adjusting.
    When I send one of my peers and email I like to add a reminder for myself to do with follow-up in case the person does not reply.  Is there a way to do this with mail application? 

    You could drag the sent message to Reminders or Calendar to create a reminder.

  • Can't figure out how to install sql server for PHP

    I am trying to install SQL Server for PHP on a server with Server 2008 32 bit.  The exe from http://www.microsoft.com/en-us/download/details.aspx?id=20098 keeps tell me that it is an invalid win32 application.  I've tried running as administrator
    as well.  Somewhere I read you need to open the exe with 7zip.  I was able to do that and see all the files in there, but where do I go from there?
    Thanks
    Mike
    edit: forgot to mention I'm using Apache for my server

    That solution is obsolete. The current solution is here:
    http://msdn.microsoft.com/en-us/sqlserver/ff657782.aspx
    bill ross

  • I purchased Martha Stewart Living for IPad then realized it was free for me if I already received it in the mail. Now I need help in canceling the order and am having trouble finding the best way to do this easily and quickly. Please help.

    How can I most easily get refunded for subscribing to Martha Stewart Living on iPad in error?

    Normally there are no refunds, but you can try your luck.
    You can report issues with your iTunes Store purchases directly through the iTunes Store:
      http://support.apple.com/kb/HT1933

Maybe you are looking for