(To the esteemed members of the MySQL mailing list, my sincere apologies
for the length of this post-- SG)

Luc,

You are killing yourself with all of those decisions.

Take this for example:
, IF(
      CONTACT_X_CUSTOMER.ID_ADDRESS > 0
      , IF(
            CONTACT_X_CUSTOMER_ADDRESS.AddrLine2 IS NULL OR
TRIM(CONTACT_X_CUSTOMER_ADDRESS.AddrLine2) = ''
            , ''
            , CONCAT(
                  ' '
                  , CONTACT_X_CUSTOMER_ADDRESS.AddrLine2
            )
      )
      , IF(
            CUSTOMER.ID_ADDRESS_SHIPTO > 0
            , IF(
                  SHIPTO_ADDRESS.AddrLine2 IS NULL OR
TRIM(SHIPTO_ADDRESS.AddrLine2) = ''
                  , ''
                  , CONCAT(
                        ' '
                        , SHIPTO_ADDRESS.AddrLine2
                  )
            )
            , IF(
                  MAIN_ADDRESS.AddrLine2 IS NULL OR
TRIM(MAIN_ADDRESS.AddrLine2) = ''
                  , ''
                  , CONCAT(
                        ' '
                        , MAIN_ADDRESS.AddrLine2
                  )
            )
      )
)

can all be condensed to
RTRIM(ISNULL(CONCAT('  ',if(CONTACT_X_CUSTOMER.ID_ADDRESS >
0,CONTACT_X_CUSTOMER_ADDRESS.AddrLine2, IF(CUSTOMER.ID_ADDRESS_SHIPTO > 0,
SHIPTO_ADDRESS.AddrLine2, MAIN_ADDRESS.AddrLine2))),''))

Breaking that down from the inside out:
Choose which field to return based on the available IDs: IF(IF())
Add leading spaces: CONCAT()
Check for NULL. If it is, make it an empty string: ISNULL()
Collapse any remaining strings that are all blanks to an empty string but
leave your leading spaces: RTRIM()

Your JOINs seem OK, you have 3 sets of  addresses to check and 4 tables per
address so 12 joins is not unusual for your data set. I am not fond of the
big IF() clause in your 3rd join but I think you said in a follow-up letter
that you didn't mean for that to be in there so I am not sure if I need to
comment on it or not.

You may end up with a faster set of results and code that's easier to
maintain if you UNION the three sets of addresses together rather than try
to return everything all at once. Something like:

(SELECT CONTACT_X_CUSTOMER.ID
      , CONTACT.LastName AS 'Last Name'
      , CONTACT.FirstName AS 'First Name'
      , CONTACT_X_CUSTOMER.Email AS 'Email'
      , CONTACT_X_CUSTOMER.Active AS 'Active'
      , ADDRESS.AddrLine1
            + RTRIM(CONCAT('  ',ISNULL(ADDRESS.AddrLine2,'')))
            + RTRIM(CONCAT('  ',ISNULL(ADDRESS.AddrLine3,''))) as 'Address'
      , RTRIM(CONCAT('  ',ISNULL(CITY.Name,''))) as 'City'
      , RTRIM(CONCAT('  ',ISNULL(PROVINCE.Name,''))) as 'Province'
      , RTRIM(CONCAT('  ',ISNULL(COUNTRY.Name,''))) as 'Country'
      , RTRIM(CONCAT('  ',ISNULL(ADDRESS.PostalCode,''))) as 'Postal Code'
      . 'Contact Address' as 'Address Type'
FROM CONTACT_X_CUSTOMER
LEFT JOIN CONTACT
      ON CONTACT_X_CUSTOMER.ID_CONTACT = CONTACT.ID
LEFT JOIN CUSTOMER
      ON CONTACT_X_CUSTOMER.ID_CUSTOMER = CUSTOMER.ID
LEFT JOIN ADDRESS
      ON CONTACT_X_CUSTOMER.ID_ADDRESS = ADDRESS.ID
LEFT JOIN CITY
      ON ADDRESS.ID_CITY = CITY.ID
LEFT JOIN PROVINCE
      ON ADDRESS.ID_PROVINCE = PROVINCE.ID
LEFT JOIN COUNTRY
      ON ADDRESS.ID_COUNTRY =  COUNTRY.ID
WHERE CONTACT_X_CUSTOMER.ID_CUSTOMER =  17
      AND CONTACT_X_CUSTOMER.ID_ADDRESS > 0
)

UNION

(SELECT CONTACT_X_CUSTOMER.ID
      , CONTACT.LastName AS 'Last Name'
      , CONTACT.FirstName AS 'First Name'
      , CONTACT_X_CUSTOMER.Email AS 'Email'
      , CONTACT_X_CUSTOMER.Active AS 'Active'
      , ADDRESS.AddrLine1
            + RTRIM(CONCAT('  ',ISNULL(ADDRESS.AddrLine2,'')))
            + RTRIM(CONCAT('  ',ISNULL(ADDRESS.AddrLine3,''))) as 'Address'
      , RTRIM(CONCAT('  ',ISNULL(CITY.Name,''))) as 'City'
      , RTRIM(CONCAT('  ',ISNULL(PROVINCE.Name,''))) as 'Province'
      , RTRIM(CONCAT('  ',ISNULL(COUNTRY.Name,''))) as 'Country'
      , RTRIM(CONCAT('  ',ISNULL(ADDRESS.PostalCode,''))) as 'Postal Code'
      . 'Customer Ship To' as 'Address Type'
FROM CONTACT_X_CUSTOMER
LEFT JOIN CONTACT
      ON CONTACT_X_CUSTOMER.ID_CONTACT = CONTACT.ID
LEFT JOIN CUSTOMER
      ON CONTACT_X_CUSTOMER.ID_CUSTOMER = CUSTOMER.ID
LEFT JOIN ADDRESS
      ON CUSTOMER.ID_ADDRESS_SHIPTO = ADDRESS.ID
LEFT JOIN CITY
      ON ADDRESS.ID_CITY = CITY.ID
LEFT JOIN PROVINCE
      ON ADDRESS.ID_PROVINCE = PROVINCE.ID
LEFT JOIN COUNTRY
      ON ADDRESS.ID_COUNTRY =  COUNTRY.ID
WHERE CONTACT_X_CUSTOMER.ID_CUSTOMER =  17
      AND NOT CONTACT_X_CUSTOMER.ID_ADDRESS>0
      AND  CUSTOMER.ID_ADDRESS_SHIPTO > 0
)

UNION

(SELECT CONTACT_X_CUSTOMER.ID
      , CONTACT.LastName AS 'Last Name'
      , CONTACT.FirstName AS 'First Name'
      , CONTACT_X_CUSTOMER.Email AS 'Email'
      , CONTACT_X_CUSTOMER.Active AS 'Active'
      , ADDRESS.AddrLine1
            + RTRIM(CONCAT('  ',ISNULL(ADDRESS.AddrLine2,'')))
            + RTRIM(CONCAT('  ',ISNULL(ADDRESS.AddrLine3,''))) as 'Address'
      , RTRIM(CONCAT('  ',ISNULL(CITY.Name,''))) as 'City'
      , RTRIM(CONCAT('  ',ISNULL(PROVINCE.Name,''))) as 'Province'
      , RTRIM(CONCAT('  ',ISNULL(COUNTRY.Name,''))) as 'Country'
      , RTRIM(CONCAT('  ',ISNULL(ADDRESS.PostalCode,''))) as 'Postal Code'
      . 'Customer Main' as 'Address Type'
FROM CONTACT_X_CUSTOMER
LEFT JOIN CONTACT
      ON CONTACT_X_CUSTOMER.ID_CONTACT = CONTACT.ID
LEFT JOIN CUSTOMER
      ON CONTACT_X_CUSTOMER.ID_CUSTOMER = CUSTOMER.ID
LEFT JOIN ADDRESS
      ON CONTACT_X_CUSTOMER.ID_ADDRESS_MAIN = ADDRESS.ID
LEFT JOIN CITY
      ON ADDRESS.ID_CITY = CITY.ID
LEFT JOIN PROVINCE
      ON ADDRESS.ID_PROVINCE = PROVINCE.ID
LEFT JOIN COUNTRY
      ON ADDRESS.ID_COUNTRY =  COUNTRY.ID
WHERE CONTACT_X_CUSTOMER.ID_CUSTOMER =  17
      AND NOT CONTACT_X_CUSTOMER.ID_ADDRESS>0
      AND NOT CUSTOMER.ID_ADDRESS_SHIPTO>0
)
ORDER BY `Last Name`

As you can see, there are only a couple of differences between each of the
queries in this UNION. One is which field is used to create the LEFT JOIN
for the ADDRESS table. The other is in the WHERE clause for each SELECT.
The last column, 'Address Type', is returning a constant for each of the
inner queries as each query should only return one type of address. The
parentheses surrounding the inner queries are required in order to use the
final ORDER BY clause.

This style of design will allow you to debug each inner query individually
and because it's based on the same template for each element of the UNION
should be easier to maintain.  One new idea just hit me.  We are doing an
awful lot of string manipulations during the course of this query. You may
want to experiment with your rendering language (if this query is to be
reprocessed for output as say HTML or a text file) to see if it can mangle
those strings faster.

Another way of avoiding so many string manipulations on the initial query
is to do two queries in sequence. The first query would select only the ID
number of the address we need (<the fields we need from the CONTACT and
CUSTOMER TABLES>, ID_ADDRESS,  and 'Address Type') into a temp table then
you do the full set of JOINS on the temp table to fill in the rest of the
textual address information. Something like this:

CREATE TEMPORARY TABLE tmpContactIDS
SELECT CONTACT_X_CUSTOMER. ID
      , CONTACT.LastName AS 'Last Name'
      , CONTACT.FirstName AS 'First Name'
      , CONTACT_X_CUSTOMER.Email AS 'Email'
      , CONTACT_X_CUSTOMER.Active AS 'Active'
      , IF(CONTACT_X_CUSTOMER.ID_ADDRESS > 0, ID_ADDRESS,
IF(CUSTOMER.ID_ADDRESS_SHIPTO>0,ID_ADDRESS_SHIPTO,ID_ADDRESS_MAIN)) as
'ADDRESS_ID'
      , IF(CONTACT_X_CUSTOMER.ID_ADDRESS > 0, 'Contact Address',
IF(CUSTOMER.ID_ADDRESS_SHIPTO>0, 'Customer Ship To', 'Customer Main')) as
'Address Type'
FROM CONTACT_X_CUSTOMER
LEFT JOIN CONTACT
      ON CONTACT_X_CUSTOMER.ID_CONTACT = CONTACT.ID
LEFT JOIN CUSTOMER
      ON CONTACT_X_CUSTOMER.ID_CUSTOMER = CUSTOMER.ID
WHERE CONTACT_X_CUSTOMER.ID_CUSTOMER =  17

SELECT ID
      ,`Last Name`
      , `First Name`
      , `Email`
      , `Active`
      , ADDRESS.AddrLine1
            + RTRIM(CONCAT('  ',ISNULL(ADDRESS.AddrLine2,'')))
            + RTRIM(CONCAT('  ',ISNULL(ADDRESS.AddrLine3,''))) as 'Address'
      , RTRIM(CONCAT('  ',ISNULL(CITY.Name,''))) as 'City'
      , RTRIM(CONCAT('  ',ISNULL(PROVINCE.Name,''))) as 'Province'
      , RTRIM(CONCAT('  ',ISNULL(COUNTRY.Name,''))) as 'Country'
      , RTRIM(CONCAT('  ',ISNULL(ADDRESS.PostalCode,''))) as 'Postal Code'
FROM tmpContactIDS IDS
LEFT JOIN ADDRESS
      ON IDS.ADDRESS_ID = ADDRESS.ID
LEFT JOIN CITY
      ON ADDRESS.ID_CITY = CITY.ID
LEFT JOIN PROVINCE
      ON ADDRESS.ID_PROVINCE = PROVINCE.ID
LEFT JOIN COUNTRY
      ON ADDRESS.ID_COUNTRY =  COUNTRY.ID

That way you only have to hit the database twice, once to get the header
info (everything but the actual address info) then once again to resolve
the ID of the address you selected. The second hit is *much* lighter as you
are JOINING the address information tables on a *much* smaller result set.

I do have one nagging question, though.... Why do you insist on LEFT
joining the CONTACT and CUSTOMER tables to CONTACT_X_CUSTOMER? I would
assume that if a record exists in CONTACT_X_CUSTOMER then there would be
corresponding records in both of those tables.

I know this has been a huge post but you have a rather involved query to
work through. If the email system re-wrapped everything to that it became
illegible email me directly and I will attach these as text files and mail
them straight back.

Yours,
Shawn Green
Database Administrator
Unimin Corporation - Spruce Pine



                                                                                       
                                      
                      "Luc Foisy"                                                      
                                      
                      <[EMAIL PROTECTED]        To:       <[EMAIL PROTECTED]>, 
"MYSQL-List (E-mail)"                       
                      -magic.com>                  <[EMAIL PROTECTED]>                 
                                  
                                                  cc:                                  
                                      
                      06/02/2004 02:09 PM         Fax to:                              
                                      
                                                  Subject:  RE: JOINing complication, 
help please                            
                                                                                       
                                      





As horible as it looks, this is what I came out with...

SELECT CONTACT_X_CUSTOMER.ID, CONTACT.LastName AS 'Last Name',
CONTACT.FirstName AS 'First Name', -----------------snip----------------
 =  17  ORDER BY CONTACT.LastName

(of course checking for NULL and TRIM() sure adds to the select)

I originally had it looking much better...

LEFT JOIN ADDRESS ON IF(CONTACT_X_CUSTOMER.ID_ADDRESS > 0,
CONTACT_X_CUSTOMER.ID_ADDRESS = ADDRESS.ID, IF(CUSTOMER.ID_ADDRESS_SHIPTO >
0, CUSTOMER.ID_ADDRESS_SHIPTO = ADDRESS.ID, CUSTOMER.ID_ADDRESS_MAIN =
ADDRESS.ID)

This actually worked great, I only had to refer to ADDRESS once in the
select for output, I only had to join the other tables CITY, PROVINCE,
COUNTRY once on ADDRESS too.

BUT the darned thing wouldn't see the indexes on the related address
fields, so when I populated the ADDRESS table with 100000 records, the
return took much longer than desired. IF it did pay attention to the
indexes like I expected, then it would have been the much preferable
choice, but it didn't, so I had to do many extra joins and use aliases and
get a monstrosity :)


( i do really appologize for that big spew of SQL )


-----Original Message-----
From: [EMAIL PROTECTED] [mailto:[EMAIL PROTECTED]
Sent: Wednesday, June 02, 2004 11:37 AM
To: Luc Foisy; MYSQL-List (E-mail)
Subject: Re: JOINing complication, help please



Sorry to reply to myslef but I just saw my own typo. Here is a better
example statement:

SELECT x.ID_ADDRESS, c.customerName, COALESCE(s.address, a.address,
'none') as address
FROM Customer c
INNER JOIN CONTACT_X_CUSTOMER x
      ON c.ID = x.CUSTOMER_ID
LEFT JOIN Address a
      ON a.ID = x.ID_ADDRESS
LEFT JOIN Address s
      ON s.ID = x.ID_ADDRESS_SHIPTO

(the problem was: the s and the a tables are aliases of the same table so
they should have had the same column names. SORRY !!!)




                      [EMAIL PROTECTED]

                                               To:       "Luc Foisy"
<[EMAIL PROTECTED]>
                      06/02/2004 11:31         cc:       "MYSQL-List
(E-mail)" <[EMAIL PROTECTED]>
                      AM                       Fax to:

                                               Subject:  Re: JOINing
complication, help please






Luc,

This looks like you want a list of all Customers with Contacts (because you
are basing it on the CONTACT_X_CUSTOMER table) and you want to show the
Address (if it exists) or the Shipping Address (if it exists) instead of
the Address? Am I close?

If I want to get one of two or more result choices in a column, I use an
IF(), a CASE...END, an IFNULL(), a NULLIF() or a COALESCE() statement,
depending on what it is I am choosing between.

In your case I think you want to chose which address to use based on their
existence, in this case, if it exists, it won't be null:

SELECT x.ID_ADDRESS, c.customerName, COALESCE(s.ship_to_address, a.address,
'none') as address
FROM Customer c
INNER JOIN CONTACT_X_CUSTOMER x
      ON c.ID = x.CUSTOMER_ID
LEFT JOIN Address a
      ON a.ID = x.ID_ADDRESS
LEFT JOIN Address s
      ON s.ID = x.ID_ADDRESS_SHIPTO


In this statement, the COALESCE statement will resolve to be the first
non-null expression in the list. If there is no Address that matches your
_X_ table's ID_ADDRESS_SHIP to then all of the columns in the table aliased
as "s" will be NULL,  Same with ID_ADDRESS and the table aliased as  "a".
If neither address exists the string 'none' is the result.

HTH!

Shawn Green
Database Administrator
Unimin Corporation - Spruce Pine




                      "Luc Foisy"

                      <[EMAIL PROTECTED]        To:       "MYSQL-List
(E-mail)" <[EMAIL PROTECTED]>
                      -magic.com>                 cc:

                                                  Fax to:

                      06/02/2004 10:46 AM         Subject:  JOINing
complication, help please







CONTACT_X_CUSTOMER.ID_ADDRESS
CUSTOMER.ID_ADDRESS_SHIPTO
CUSTOMER.ID_ADDRESS_MAIN

What I would like is to be able to JOIN conditionally based on the
absence/presence of reference

SELECT ADDRESS.ID FROM CONTACT_X_CUSTOMER LEFT JOIN CUSTOMER ON
CONTACT_X_CUSTOMER.ID_CUSTOMER = CUSTOMER.ID

(thats the simple part, below is what I want, but it doesn't work of
course, but the logic is kinda there)

IF(CONTACT_X_CUSTOMER.ID_ADDRESS > 0, LEFT JOIN ADDRESS ON
CUSTOMER_X_CONTACT.ID_ADDRESS = ADDRESS.ID,
             IF(CUSTOMER.ID_ADDRESS_SHIPTO > 0, LEFT JOIN ADDRESS ON
CUSTOMER.ID_ADDRESS_SHIPTO = ADDRESS.ID,
                         LEFT JOIN ADDRESS ON CUSTOMER.ID_ADDRESS_MAIN =
ADDRESS.ID))

Sorry if I didn't break that up clear like, it was an attempt to make it
more clear :)

Is there ANY way I could get this to work (on the lastest version of the
3.x mysql)


--
MySQL General Mailing List
For list archives: http://lists.mysql.com/mysql
To unsubscribe:    http://lists.mysql.com/[EMAIL PROTECTED]







--
MySQL General Mailing List
For list archives: http://lists.mysql.com/mysql
To unsubscribe:    http://lists.mysql.com/[EMAIL PROTECTED]












-- 
MySQL General Mailing List
For list archives: http://lists.mysql.com/mysql
To unsubscribe:    http://lists.mysql.com/[EMAIL PROTECTED]

Reply via email to