Understanding a Odd JOIN

  • This may not seem odd to many, but it is the first time I have seen it and I need to rework it in such a way that I can take the result set and put it in a table then use the variables after the insert.

    I just don't really understand the structure of these joins. Any help in refactoring or explaining would be awesome ! I apologize for not having any sample data.

    ***** The joins with the () are whats confusing me.

    Thanks !

    DECLARE @AsOfDate DATE = GETDATE()

    SELECT *

    FROMTransactionTable tt

    LEFT JOIN (PatientTable pt

    INNER JOIN Batch b ON pt.BatchId = b.BatchId ) ON t1.PatientVisitProcsId = pt.PatientVisitProcsId AND b.Entry < DATEADD(d, 1, @AsOfDate)

    INNER JOIN Actions A ON tt.TransactionsId = a.TransactionsId AND a.Action <> 'N'

    INNER JOIN VisitAction vt ON a.VisitTransactionsId = vt.VisitTransactionsId

    INNER JOIN (PaymentMethod pm

    INNER JOIN Batch b2 ON pm.BatchId = b2.BatchId) ON vt.PaymentMethodId = pm.PaymentMethodId AND b2.Entry < DATEADD(d, 1, @AsOfDate)

    INNER JOIN PatientVisit pv ON vt.PatientVisitId = pv.PatientVisitId

    ***SQL born on date Spring 2013:-)

  • thomashohner (8/9/2016)


    This may not seem odd to many, but it is the first time I have seen it and I need to rework it in such a way that I can take the result set and put it in a table then use the variables after the insert.

    I just don't really understand the structure of these joins. Any help in refactoring or explaining would be awesome ! I apologize for not having any sample data.

    ***** The joins with the () are whats confusing me.

    Thanks !

    DECLARE @AsOfDate DATE = GETDATE()

    SELECT *

    FROMTransactionTable tt

    LEFT JOIN (PatientTable pt

    INNER JOIN Batch b ON pt.BatchId = b.BatchId ) ON t1.PatientVisitProcsId = pt.PatientVisitProcsId AND b.Entry < DATEADD(d, 1, @AsOfDate)

    INNER JOIN Actions A ON tt.TransactionsId = a.TransactionsId AND a.Action <> 'N'

    INNER JOIN VisitAction vt ON a.VisitTransactionsId = vt.VisitTransactionsId

    INNER JOIN (PaymentMethod pm

    INNER JOIN Batch b2 ON pm.BatchId = b2.BatchId) ON vt.PaymentMethodId = pm.PaymentMethodId AND b2.Entry < DATEADD(d, 1, @AsOfDate)

    INNER JOIN PatientVisit pv ON vt.PatientVisitId = pv.PatientVisitId

    It's no different from any other place that parentheses are used. It changes the logical order of processing. The joins inside parentheses must be (logically) evaluated before joins outside of the parentheses.

    Drew

    PS: Technically, the parentheses aren't needed, because it's actually the ON clauses which control the order, the parentheses just make it easier for humans to understand the logic that is being used.

    PPS: The logical order of processing doesn't have to match the physical ordering of processing as long as the results are guaranteed to be the same.

    J. Drew Allen
    Business Intelligence Analyst
    Philadelphia, PA

  • To add to drew's answer, if your issue is with that JOIN within a JOIN condition, I would think of it as building a result-set from the inner JOIN, and then using that result-set in the outer JOIN condition.

    Here's an example of that type of join being refactored (without the parentheses, which drew has described):

    declare @stores table (

    store_id int

    , store_name nvarchar(500)

    )

    declare @customers table (

    customer_id int

    , store_id int

    , customer_name nvarchar(500)

    )

    declare @accounts table (

    account_id int

    , customer_id int

    , account_name nvarchar(500)

    , account_credit decimal(12,5)

    )

    insert into @stores(store_id, store_name)

    select 1, 'London'

    union all select 2, 'Cambridge'

    union all select 3, 'Leeds'

    union all select 4, 'Brighton'

    union all select 5, 'Oxford'

    insert into @customers(customer_id, store_id, customer_name)

    select 1, 1, 'Jane Doe'

    union all select 2, 1, 'Jim Smith'

    union all select 3, 1, 'Michael Jones'

    union all select 4, 2, 'Timothy Smith'

    union all select 5, 3, 'Kim Peters'

    union all select 5, 4, 'Kim Peters'

    insert into @accounts(account_id, customer_id, account_name, account_credit)

    select 1, 1, 'Savings', 50

    union all select 2, 1, 'Cheque', 12

    union all select 3, 2, 'Savings', 1

    union all select 4, 3, 'Cheque', 120

    union all select 5, 4, 'Credit', 73

    union all select 6, 4, 'Savings', 52

    union all select 7, 5, 'Credit', 63

    -- Get the details of all accounts with a balance greater than 50, including any stores that don't have balances.

    -- By joining to customers+accounts on the condition that the customer has an account with an account credit of above 50 and the customer's store_id matches stores.store_id

    -- This is probably the option I would use, even though it's harder to read.

    select

    stores.store_id

    , stores.store_name

    , customers.customer_id

    , customers.customer_name

    , accounts.account_id

    , accounts.account_name

    , accounts.account_credit

    from @stores as stores

    left join @customers as customers

    inner join @accounts as accounts

    on customers.customer_id = accounts.customer_id

    on stores.store_id = customers.store_id

    and accounts.account_credit > 50

    order by stores.store_id,

    customers.customer_id,

    accounts.account_id

    -- By joining to customers+accounts in a subquery. This is a more common way of doing what has been done above (at least I've seen it more where I work).

    select

    stores.store_id

    , stores.store_name

    , customeraccounts.customer_id

    , customeraccounts.customer_name

    , customeraccounts.account_id

    , customeraccounts.account_name

    , customeraccounts.account_credit

    from @stores as stores

    left join

    (

    select

    customers.customer_id,

    customers.customer_name,

    customers.store_id,

    accounts.account_id,

    accounts.account_name,

    accounts.account_credit

    from @customers as customers

    inner join @accounts as accounts

    on customers.customer_id = accounts.customer_id

    where accounts.account_credit > 50

    ) customeraccounts

    on stores.store_id = customeraccounts.store_id

    order by stores.store_id,

    customeraccounts.customer_id,

    customeraccounts.account_id

    -- Do the same as above, with filtering done in the WHERE clause

    select

    stores.store_id

    , stores.store_name

    , customers.customer_id

    , customers.customer_name

    , accounts.account_id

    , accounts.account_name

    , accounts.account_credit

    from @stores as stores

    left join @customers as customers

    on stores.store_id = customers.store_id

    left join @accounts as accounts

    on customers.customer_id = accounts.customer_id

    where accounts.account_credit > 50

    or not exists (

    select *

    from @accounts accounts

    where customers.customer_id = accounts.customer_id

    )

    order by stores.store_id,

    customers.customer_id,

    accounts.account_id

  • Thanks guys I had just never seen it before and so the syntax threw me. It makes sense now. Thanks !!

    ***SQL born on date Spring 2013:-)

  • thomashohner (8/9/2016)


    Thanks guys I had just never seen it before and so the syntax threw me. It makes sense now. Thanks !!

    INNER JOINs and CROSS JOINS are associative, so there's no need to use parens if you only have those types of joins. And in cases where OUTER JOINS are used, they're usually placed last, so that there is no need to change the processing order. Also, some people may use CTEs, derived tables, or views to avoid this type of construct. All of those contribute to the fact that these constructions aren't commonplace.

    Drew

    J. Drew Allen
    Business Intelligence Analyst
    Philadelphia, PA

Viewing 5 posts - 1 through 4 (of 4 total)

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