Search code examples
sqlsql-serversubquerysql-viewcorrelated-subquery

Slow SQL view using several subqueries


There is probably a much better way to create these views. I have limited SQL experience so this is the way I designed it, I am hoping some of you SQL gurus can point me in a more efficient direction.

I essentially have 3 tables (sometimes 4) in my view, here is the essential structure:

Table USER
USER_ID | EMAIL | PASSWORD | CREATED_DATE

(Indexes: USER_ID)

Table USER_META
ID | USER_ID | NAME | VALUE

(Indexes: ID,USER_ID,NAME)

Table USER_SCORES
ID | USER_ID | GAME_ID | SCORE | CREATED_DATE

(Indexes: ID,USER_ID)

All the tables use the first ID column as an auto-increment primary key.

The second table "USER_META" is where I keep all the contact info and other misc. Primarily it is first_name,last_name, street,city, etc. - Depending on the user this could be 4 items or 140, which is why I use this table instead of having 150 columns in my USER table.

For reports, searching and editing I need about 20 values from USER_META, so I have views that look like this:

View V_USR_META
select USER_ID,EMAIL,
(select VALUE from USER_META 
   where NAME = 'FIRST_NAME' and USER_ID = u.USER_ID) as first_name,
(select VALUE from USER_META 
   where NAME = 'LAST_NAME'  and USER_ID = u.USER_ID) as last_name,
(select VALUE from USER_META 
   where NAME = 'CITY'  and USER_ID = u.USER_ID) as city,
(select VALUE from USER_META 
   where NAME = 'STATE'  and USER_ID = u.USER_ID) as state,
(select VALUE from USER_META 
   where NAME = 'ZIP'  and USER_ID = u.USER_ID) as zip,
    /* 10 more selects for different meta values here */
(select max(SCORE) from USER_SCORES 
   where USER_ID = u.USER_ID) as high_score,
(select top (1) CREATED_DATE from USER_SCORES 
   where USER_ID = u.USER_ID 
   order by id desc) as last_game
from USER u

This get's pretty slow, and there are actually many more sub queries, this is just to illustrate the query. I also have to query a few other tables to get misc. info about the user.

I use the view when searching for a user, searches use name or userid or email or score, etc. I also use it to populate the user information screen when I present all the data in one place.

So - Is there a better way to write the view?


Solution

  • An alternative to all of those correlated subqueries would be to use max with case:

    select u.USER_ID,
           u.EMAIL,
           max(case when um.name = 'FIRST_NAME' then um.value end) first_name,
           max(case when um.name = 'LAST_NAME' then um.value end) last_name
           ...
    from USER u
           left join USER_META um 
                    on u.user_id = um.user_id
    group by u.user_id, u.email
    

    Then you could add the user_scores results:

    select u.USER_ID,
           u.EMAIL,
           max(case when um.name = 'FIRST_NAME' then um.value end) first_name,
           max(case when um.name = 'LAST_NAME' then um.value end) last_name
           ...,
           max(us.score) maxscore,
           max(us.created_date) maxcreateddate
    from USER u
           left join USER_META um 
                    on u.user_id = um.user_id
           left join USER_SCORES us
                    on u.user_id = us.user_id
    group by u.user_id, u.email