Find (Count) Joins inside Views

  • Good Afternoon All,

    I want to be able to count the number of joins used in views. We have a lot of views that are legacy and are awful. I was hoping to provide some kind of insight into what views could be improved or not used anymore -- using the number of joins as an indicator of performance.

    I know that's not a fool proof way but in this environment poor performance is correllated with monstrous views (10+ joins each, and somtimes views that join two other monstrous views). The majority that we've found were used because its easier.

    So my question is how to get a top level view of this without inspecting each view by hand. Is this possible? We have the query store enabled as well, should that be a route to take.

     

    Cheers

    Alex

  • Thanks for posting your issue and hopefully someone will answer soon.

    This is an automated bump to increase visibility of your question.

  • This seems to almost work, but the counts are a bit higher than the actual number of 'JOIN' strings.

    No time to improve it right now, but might be "good enough" for you to get started. You may have multiple rows for the same view because the view code is stored in multiple records.

    select  name,  
    (LEN(text) - LEN(REPLACE(text,'JOIN ','')))/COALESCE(NULLIF(LEN('JOIN '), 0), 1) as 'Count'
    from syscomments com
    join sysobjects obj on com.id = obj.id
    where text like '%JOIN %' and
    TYPE = 'V'
    order by name

     

  • I think should be pretty accurate, at least without going to a lot more trouble to write.  Yes, it uses recursion, because we need to find every JOIN.  If somehow you have more than 100 JOINs in a view, you'll need to add a MAXRECURSION option/clause.

    ;WITH cte_find_joins AS (
    SELECT v.object_id, view_def, PATINDEX('%[^A-Z0-9$#_]JOIN[^A-Z0-9$#_]%', view_def) AS view_join
    FROM sys.views v
    CROSS APPLY ( SELECT OBJECT_DEFINITION(v.object_id) As view_def ) AS view_def
    WHERE PATINDEX('%[^A-Z0-9$#_]JOIN[^A-Z0-9$#_]%', view_def) > 0
    UNION ALL
    SELECT object_id, view_def,
    view_join + PATINDEX('%[^A-Z0-9$#_]JOIN[^A-Z0-9$#_]%', SUBSTRING(view_def, view_join + 6, 2000000000))
    FROM cte_find_joins
    WHERE PATINDEX('%[^A-Z0-9$#_]JOIN[^A-Z0-9$#_]%', SUBSTRING(view_def, view_join + 6, 2000000000)) > 0
    )
    SELECT OBJECT_NAME(object_id) AS view_name, COUNT(*) AS join_count
    FROM cte_find_joins
    GROUP BY object_id
    ORDER BY view_name

    SQL DBA,SQL Server MVP(07, 08, 09) "It's a dog-eat-dog world, and I'm wearing Milk-Bone underwear." "Norm", on "Cheers". Also from "Cheers", from "Carla": "You need to know 3 things about Tortelli men: Tortelli men draw women like flies; Tortelli men treat women like flies; Tortelli men's brains are in their flies".

  • neither of the options above will capture cases where the "joins" are old style.

    and why not use the system dependency tables for it?

    SELECT schema_name(o.schema_id) as  referencing_schema_name
    , OBJECT_NAME(sed.referencing_id) AS referencing_entity_name
    , count(*) as Number_Of_Joins
    , sum(case when o1.type = 'IF' then 1 else 0 end) as count_Inline_Function
    , sum(case when o1.type = 'S' then 1 else 0 end) as count_System_Table
    , sum(case when o1.type = 'SN' then 1 else 0 end) as count_Synonym
    , sum(case when o1.type = 'U' then 1 else 0 end) as count_User_Table
    , sum(case when o1.type = 'V' then 1 else 0 end) as count_View
    , sum(case when o1.type = 'TF' then 1 else 0 end) as count_Table_Valued_Function
    FROM sys.sql_expression_dependencies sed
    INNER JOIN sys.objects o
    ON sed.referencing_id = o.object_id
    left outer join sys.objects o1 -- only works for locally referenced objects
    on sed.referenced_id = o1.object_id
    and o1.type in (
    -- only look for these types - they would most likely be used in joins
    'IF' -- SQL inline table-valued function
    , 'S' -- System base table
    , 'SN' -- Synonym
    , 'U' -- Table (user-defined)
    , 'V' -- View
    , 'TF' -- SQL table-valued-function
    )
    where o.type_desc = 'View'
    group by schema_name(o.schema_id)
    , OBJECT_NAME(sed.referencing_id)
  • Wouldn't that count every reference as a "join", including a select from a single table?  And any other single select from a table.

    SELECT ...

    FROM dbo.table1

    SELECT ...

    FROM dbo.table1

    WHERE EXISTS(SELECT 1 FROM sys.tables WHERE name = 'table2') /*note that this is not a join*/

     

    SQL DBA,SQL Server MVP(07, 08, 09) "It's a dog-eat-dog world, and I'm wearing Milk-Bone underwear." "Norm", on "Cheers". Also from "Cheers", from "Carla": "You need to know 3 things about Tortelli men: Tortelli men draw women like flies; Tortelli men treat women like flies; Tortelli men's brains are in their flies".

  • ScottPletcher wrote:

    Wouldn't that count every reference as a "join", including a select from a single table?  And any other single select from a table.

    SELECT ...

    FROM dbo.table1

    SELECT ...

    FROM dbo.table1

    WHERE EXISTS(SELECT 1 FROM sys.tables WHERE name = 'table2') /*note that this is not a join*/

    for first case yes - but sql I posted can easily exclude those (having count(*) > 1)

    as for the second case - I would consider that to be a join anyway - just not a "direct" join, but it is linking 2 tables

    consider the following

    select (select name from reftable where refid = a.id1) as id_1

    , (select name from reftable where refid = a.id2) as id_2

    , (select name from reftable where refid = a.id3) as id_3

    , (select name from reftable where refid = a.id4) as id_4

    from invoices a

    that is 4 joins - my query would would list 2 tables used - the ones above would list only 1 table - so neither is good in this case

    a combination of my code plus a string search for

    "join, union, outer apply, cross apply, exists, in" -- not all of these will be other tables but they can be - lots of false positives specially for the "in, outer apply"

    could give a better result - but string search alone even including the ones I mention here is not enough.

     

  • That's why I put the note: my EXISTS example does NOT reference an outer table.  A rare situation perhaps, but not impossible, more likely:

    WHERE EXISTS(SELECT 1 FROM sys.tables WHERE name = @variable1)

    This query just references a parameter / variable, NOT another query, therefore it is NO type of join.

    but sql I posted can easily exclude those (having count(*) > 1)

    But then how do you show views with a single JOIN statement?

    What about joins to queries that use several tables?  It's only 1 join but it will be lots of object references:

    SELECT ...

    FROM ...

    INNER JOIN (

    SELECT key_col, SUM(some_amount) AS ..., SUM(some_other_amount) AS ...

    FROM table1...

    INNER JOIN table2...

    INNER JOIN table3...

    INNER JOIN table4...

    ) AS totals1

    SQL DBA,SQL Server MVP(07, 08, 09) "It's a dog-eat-dog world, and I'm wearing Milk-Bone underwear." "Norm", on "Cheers". Also from "Cheers", from "Carla": "You need to know 3 things about Tortelli men: Tortelli men draw women like flies; Tortelli men treat women like flies; Tortelli men's brains are in their flies".

  • ScottPletcher wrote:

    That's why I put the note: my EXISTS example does NOT reference an outer table.  A rare situation perhaps, but not impossible, more likely:

    WHERE EXISTS(SELECT 1 FROM sys.tables WHERE name = @variable1)

    This query just references a parameter / variable, NOT another query, therefore it is NO type of join.

    but sql I posted can easily exclude those (having count(*) > 1)

    But then how do you show views with a single JOIN statement?

    What about joins to queries that use several tables?  It's only 1 join but it will be lots of object references:

    SELECT ...

    FROM ...

    INNER JOIN (

    SELECT key_col, SUM(some_amount) AS ..., SUM(some_other_amount) AS ...

    FROM table1...

    INNER JOIN table2...

    INNER JOIN table3...

    INNER JOIN table4...

    ) AS totals1

    Missing the point on this last one - you have multiple joins, not just 1 join.

    in this case both the "string" approach and my method would give multiple counts.

    Main difference as I said is that the system view reports objects used, not number of times the object is used.

    so queries with multiple joins to the same table will unfortunately be underreported with my code.

    but with string approach, and if using old style joins, then no joins will be reported either on this particular case.

    As I said no perfect solution and neither approach will work for all cases.

  • I would like to remind you that views themselves are not a performance issue. The query can be the culprit.

    You could inline every single view -factoring out every single one- and the performance would not change.

    In other words, a badly written view performs exactly the same as a badly written query.

  • This was removed by the editor as SPAM

  • > Missing the point on this last one - you have multiple joins, not just 1 join. <<

    It depends on how you look at it.  The view itself has only one JOIN.  My query will also miscount that one.

    As to old-style joins, it would be extraordinarily complex to try to determine those dynamically (and in any reasonable time period).  Besides, they shouldn't have been used for the past 20 years at least, so hopefully they're not an issue for nearly all shops.

    SQL DBA,SQL Server MVP(07, 08, 09) "It's a dog-eat-dog world, and I'm wearing Milk-Bone underwear." "Norm", on "Cheers". Also from "Cheers", from "Carla": "You need to know 3 things about Tortelli men: Tortelli men draw women like flies; Tortelli men treat women like flies; Tortelli men's brains are in their flies".

Viewing 12 posts - 1 through 11 (of 11 total)

You must be logged in to reply to this topic. Login to reply