MySQL LEFT JOIN issue: Retrieve workshops names in column A and a row id in column B - mysql

Apologies for the rubbish question title, but it's a bit tricky to summarise my requirement into a single line. I usually don't have an issue with MySQL JOINs but this one is throwing me.
I'm building a training feedback system and for one feature would like to display a list of all available workshops in the database, which workshops a given delegate has been assigned to and whether any feedback has been submitted by that delegate for those assigned workshops.
I could do this in a couple of queries, but I'm trying to do something a bit more elegant with a single query.
The pertinent details of my database structure:
WORKSHOPS table
id: INT
name: TINYTEXT
DELEGATES table
id: INT
name: TINYTEXT
FEEDBACK table
delegate_id: INT
workshop_id: INT
feedback: TEXT
DELEGATES_X_WORKSHOPS table
delegate_id: INT
workshop_id: INT
delegate_id and workshop_id in the tables are Foreign Keys to the DELEGATES and WORKSHOPS tables.
As any given delegate can be assigned to multiple workshops, I'm using the DELEGATES_X_WORKSHOPS table as a cross-referencing table so I can quickly search for who is assigned to any given workshop or which workshops any given delegate is assigned to.
However, I've tried LEFT JOINing a couple of different ways and I can't get a full list of workshops on the left and matches (if they exist) on the right for a given delegate_id.
Example data
Delegate Ross has delegate_id = 1
Registered workshops are
C++
PHP
ASP.NET
HTML5
JavaScript
Ross is assigned to PHP, HTML5 and JavaScript
Question 1 is this: how do I return the following for delegate_id=1:
[workshop] | [assigned]
C++ | null
PHP | TRUE
ASP.NET | null
HTML5 | TRUE
JavaScript | TRUE
(it doesn't matter right now what goes into column B, I just want a null if a particular delegate_id hasn't been assigned to a workshop).
I've used this:
SELECT
workshops.name,
delegates_x_workshops.delegate_id
FROM
workshops
LEFT JOIN
delegates_x_workshops
ON
workshops.id=delegates_x_workshops.workshop_id
WHERE
delegates_x_workshops.delegate_id=1
However I'm only returning the 3 rows where delegate_id=1, not 5 rows for all workshops.
Question 2 is a bit more involved:
Taking question 1 as a base, how would I work column C to display if feedback has been left for a workshop that Ross has been assigned to?
[workshop] | [assigned] | [givenfeedback]
C++ | null | null
PHP | TRUE | TRUE
ASP.NET | null | null
HTML5 | TRUE | null
JavaScript | TRUE | TRUE
Thanks in advance to anybody who makes it this far and has a clue what I'm blithering about. As I said, I could rattle through this with a few different queries, but I'm trying to keep things elegant.
No doubt half of this will need clarification, so ask any questions.
Thanks

For question 1, you need to move the where condition into the on clause. It is turning the left outer join into an inner join because non-matching rows have NULL values:
SELECT w.name, dxw.delegate_id
FROM workshops w LEFT JOIN
delegates_x_workshops dxw
ON w.id = dxw.workshop_id and
dxw.delegate_id = 1;
For the second question, I think this is what you want:
SELECT w.name,
(case when max(w.name = 'Ross') > 0 then 'True' end) as Assigned,
(case when count(f.workshop_id) > 0 then 'True' end) as Feedback
FROM workshops w LEFT JOIN
delegates_x_workshops dxw
ON w.id = dxw.workshop_id and
dxw.delegate_id = 1 LEFT JOIN
delegates d
on d.id = dxw.delegate_id LEFT JOIN
feedback f
on f.workshop_id = w.id
GROUP BY w.name;

For reference, here's my final query:
SELECT DISTINCT
workshops.id AS wid,
workshops.name AS workshop,
(delegates_x_workshops.delegate_id IS NOT NULL) AS assigned,
(initial_feedback.delegate_id IS NOT NULL
OR
ongoing_feedback.delegate_id IS NOT NULL) AS hasfeedback
FROM
workshops
LEFT JOIN
delegates_x_workshops
ON
workshops.id = delegates_x_workshops.workshop_id
AND
delegates_x_workshops.delegate_id = 1
LEFT JOIN
initial_feedback
ON
workshops.id = initial_feedback.workshop_id
AND
initial_feedback.delegate_id = 1
LEFT JOIN
ongoing_feedback
ON
workshops.id = ongoing_feedback.workshop_id
AND
ongoing_feedback.delegate_id = 1
ORDER BY
workshop ASC
For every workshop in the WORKSHOPS table, I'll get the id and name of the workshop, 1 or 0 if a given delegate_id is assigned and 1 or 0 if feedback of either type (I have 2 kinds) has been left for that workshop.
Scary to think that all I was missing was an AND condition on my LEFT JOIN.
Thanks again Gordon!

Related

SQL view query not working

These are my tables:
portal_users(id, first_name, last_name, email, ...,)
courses(id, name, location, capacity, ...,)
feedback_questions(id, question, ...,)
feedback_answers(id, answers, IDquestion, IDuser, IDcourse)
I want to do a view with this:
course | first_name | last_name | IDuser | IDcourse | question_1 | answer_1 | question_2 | answer_2
So far
CREATE VIEW feedback_answers_vw
as
SELECT
fa.id,
fa.answer,
fq.question,
pu.first_name,
pu.last_name,
fa.IDuser,
fa.IDcourse
FROM feedback_answers fa
INNER JOIN feedback_questions fq
ON fa.IDquestion = fq.id
INNER JOIN portal_users pu
ON fa.IDuser = pu.id
INNER JOIN courses cu
on fa.IDcourse = cu.id
GROUP BY
fa.IDcourse, fa.IDuser
This just display one question and its answers, but not all the questions that belong to the same course and user.
I could hardcode this with something like this in the SELECT statement
SELECT
fa.id,
(select question
from feedback_questions
where id = 1) as question_1,
(select question
from feedback_questions
where id = 2) as question_2,
(select question
from feedback_questions
where id = 3) as question_3,
pu.first_name,
pu.last_name,
fa.IDuser,
fa.IDcourse
But I want to do it in the right way, so I won't be changing the code everytime that a question is added.
Edit:
This is data example of my tables:
**Portal users:**
1, tom, hanks, tom_hanks#example.com, ...,
2, steven, spielberg, steven#example.com, ...,
**Courses:**
1, quality, california, 30
2, information technologies, texas, 24
**Questions:**
1, How did you find the course?, ...,
2, Do you want purchase order?, ...,
**Answers:**
1, Internet, 1, 1, 1
2, yes, 2, 1, 1
3, TV, 1, 2, 1,
4, no, 2, 2, 1,
5, Internet, 1, 1, 2
6, yes, 1, 1, 2
This are data example I want to display in the view:
course|first_name|last_name|IDuser|IDcourse|Question_1|Answer_1|Question_2|Answer_2
----------------------------------------------------------------------------------
quality | tom | hanks | 1 | 1 | How did you find the course? | Internet | Do you want purchase order? | yes
quality | steven | spielberg | 2 | 1 | How did you find the course? | TV | Do you want purchase order? | no
Information technologies | tom | hanks | 1 | 2 How did you find the course? | Internet | Do you want purchase order? | yes
I dont know if you would actually need it as a view, and here is a sample query for it.
If you look at the first part of the where clause, it is based on a minimum of question ID = 1. I then join that to the course table and portal_user table to get those fields. Since the feedback answers is the first table, we can immediately get that answer, but also join to the questions table to get the question.
So now, how to simply expand this query for future questions? Notice the LEFT-JOINs right after that for the feedback answers AGAIN, but this time as an alias "fa2" (feedback_answers2), and the JOIN clause is based on the same user, same course, but only for question ID = 2. This then joins to the questions table on the fa2.questionID to the questions table (alias fq2). And then another exact same setup but for question 3. So here, I am doing nothing but using the same tables, but just different aliases. They all start with question 1, and if there is a question 2, get it... if a question 3, get it too, expand as needed, no group by, etc.
So now if you only cared about a single course you were interested, just add that to the outermost WHERE clause, nothing else needs to change. Get 10 questions, copy/paste the LEFT-JOIN components for the corresponding question IDs.
SELECT
c.name as course,
pu.first_name,
pu.last_name,
fa1.IDUser,
fa1.IDCourse,
fq1.question as question1,
fa1.answers as answer1,
fq2.question as question2,
fa2.answers as answer2,
fq3.question as question3,
fa3.answers as answer3
from
feedback_answers fa1
JOIN courses c
ON fa1.IDCourse = c.id
JOIN portal_users pu
ON fa1.IDUser = pu.id
JOIN feedback_questions fq1
ON fa1.IDquestion = fq1.id
LEFT JOIN feedback_answers fa2
ON fa1.IDUser = fa2.IDUser
AND fa1.IDCourse = fa2.IDCourse
AND fa2.id = 2
LEFT JOIN feedback_questions fq2
ON fa2.IDquestion = fq2.id
LEFT JOIN feedback_answers fa3
ON fa1.IDUser = fa3.IDUser
AND fa1.IDCourse = fa3.IDCourse
AND fa2.id = 3
LEFT JOIN feedback_questions fq3
ON fa3.IDquestion = fq3.id
where
fa1.id = 1
After some sample data was posted, it is clear a pivot table (in access/excel known as a 'crosstab') is needed here. We could hard code a select with some case statements to cover a fixed number of questions, but that solution would have to be revisited each time a question was added or removed. #drapp has such a solution below.
However, what we really want is the number of columns returned by the view to grow and shrink with the number of questions. Unfortunately mysql doesn't have any built in functions to help us build one. The best solution I found online is http://www.artfulsoftware.com/infotree/qrytip.php?id=523. The author proposes building the sql select statement dynamically then executing it. It is actually a pretty elegant solution.
Faced with this requirement, I wouldn't do this work in SQL. The problem I foresee is that as the numbers of questions rises, more and more columns will be returned; your view would quickly slow down to a crawl, and eventually stop working altogether. I would try to transform the data outside of sql, perhaps in an etl tool, application server, or BI tool.
Or, if I had the ability to (and I never do), I would switch database engines. Here are three solutions from other engines that provide tools for creating pivot tables:
Oracle: http://www.oracle.com/technetwork/articles/sql/11g-pivot-097235.html
Postgres: http://www.postgresql.org/docs/9.1/static/tablefunc.html
SQL Server: http://blogs.msdn.com/b/spike/archive/2009/03/03/pivot-tables-in-sql-server-a-simple-sample.aspx
We just talked about views in my DB Design class and my Professor said that you can't use a join in a VIEW statement. I can't recall why not but your issues looks familiar.

visual basic/mysql displaying data

We're making a program with visual basic and we have a grid, it shows data from a database.
But now there is a problem, we're displaying numbers instead of actual words, this is easy to fix with a bit of google BUT we got 2 columns with references to the same table.
So in more details, we got one table with the references, it's name is v_Transport and has an id, relationid, carrierid (those are more then enough for this example)
the data looks like this
1 | 803 | 503
2 | 653 | 321
then we have a second table called Relations with an id and name(again those are enough)
653 | spike
321 | google
803 | stackoverflow
503 | humbletest
normaly I would do something like
select t.id, r.name
from transport t, relations r
where r.id = t.relationid
This could work if there was just one reference but there are 2 to the same table. just a small thing, I can't test my query so im pretty sure it's not working or even near working.
But my question is: what would be a good way to get the data to show the right way like
1 | stackoverflow | humbletest
2 | spike | google
And if possible with an explanation of the code and possible link to a source to get more info about the topic.
How about
select t.id, r.name relation_name, rc.name carried_name
from transport t INNER JOIN
relations r ON r.id = t.relationid INNER JOIN
relations rc ON rc.id = t.carrierid
This is way yuo join to the same reference table twice, from 2 different fields in the source table.
If you are unsure of if all the reference ids are populated, you would rathet use a LEFT JOIN
select t.id, IFNULL(r.name,'NA') relation_name, IFNULL(rc.name,'NA') carried_name
from transport t LEFT JOIN
relations r ON r.id = t.relationid LEFT JOIN
relations rc ON rc.id = t.carrierid
Then you could also use IFNULL to display what you want, if you wish.

Multiple order by SQL

I'm working on a EAV database implemented in MySQL so when I say entity, you can read that as table. Since it's a non-relational database I cannot provide any SQL for tables etc but I'm hoping to get the conceptual answer for a relational database and I will translate to EAV SQL myself.
I'm building a mini stock market system. There is an "asset" entity that can have many "demand" and "offer" entities. The asset entity also may have many "deal" entites. Each deal entity has a "share_price" attribute. Not all assets have demand, offer or deal entities.
I want to return a list of offer and demand entities, grouped by asset i.e. if an asset has 2 offers and 3 demands only 1 result will show. This must be sorted by the highest share_price of deals attached to assets of the demand or offer. Then, the highest share_price for each demand or offer is sorted overall. If an asset has demands or offers but no deals, it will be returned with NULL for share_price.
So say the data is like this:
Asset 1 has 1 offer, 1 demand and 2 deals with share_price 7.50 and 12.00
Asset 2 has 1 offer and 1 deal with share_price 8.00
Asset 3 has 3 offers and 3 demands and no deals
Asset 4 has no offers and no demand and 1 deal with share_price 13.00
I want the results:
Asset share_price
Asset 1 12.00
Asset 2 8.00
Asset 3 null
Note: Asset 4 is not in the result set because it has no offers or demands.
I know this is a complex one with I really dont want to have to go to database more than once or do any array re-ordering in PHP. Any help greatly appreciated.
Some users want to see SQL I have. Here it is but this won't make too much sense as its a specialised EAV Database.
SELECT DISTINCT data.asset_guid, r.guid_two, data.share_price FROM (
select rr.guid_one as asset_guid, max(msv.string) as share_price from market_entities ee
join market_entity_relationships rr on ee.guid = rr.guid_two
JOIN market_metadata as mt on ee.guid = mt.entity_guid
JOIN market_metastrings as msn on mt.name_id = msn.id
JOIN market_metastrings as msv on mt.value_id = msv.id
where subtype = 6 and msn.string = 'share_price' and rr.relationship = 'asset_deal'
group by
rr.guid_one
) data
left outer JOIN market_entities e on e.guid = data.asset_guid
left outer JOIN market_entity_relationships r on r.guid_one = e.guid
WHERE r.relationship = 'trade_share'
GROUP BY data.asset_guid
Without fully understanding your table structure (you should post that), looks like you just need to use a single LEFT JOIN, with GROUP BY and MAX:
SELECT a.assetname, MAX(d.share_price)
FROM asset a
LEFT JOIN deal d ON a.AssetId = d.AssetId
GROUP BY a.assetname
ORDER BY MAX(d.share_price) DESC
I'm using the assumption that your Asset table and your Deal table have a common key, in the above case, AssetId. Not sure why you'd need to join on Demand or Offer, unless those link to your Deal table. Posting your table structure would alleviate that concern...
--EDIT--
In regards to your comments, you want to only show the assets which have either an offer or a demand? If so, this should work:
SELECT a.assetname, MAX(d.share_price)
FROM asset a
LEFT JOIN deal d ON a.AssetId = d.AssetId
LEFT JOIN offer o ON o.AssetId = d.AssetId
LEFT JOIN demand de ON de.AssetId = d.AssetId
WHERE o.AssetId IS NOT NULL OR de.AssetId IS NOT NULL
GROUP BY a.assetname
ORDER BY MAX(d.share_price) DESC
This will only include the asset if it has at least an offer or at least a demand.
assuming you have 3 tables, assets, offers and shares, you can use a query like below.
SELECT asset, MAX(share_Price)
FROM assets
INNER JOIN offers ON assets.id = offers.id //requires there are offers
LEFT OUTER JOIN shares ON assets.id = shares.id // returns results even if no shares
GROUP BY asset
ORDER BY asset

Mysql Query find article follower / last with no follower

I try to make a small shop based on a provided article list - generally no problem.
BUT there are articles in this list, which have newer versions with new article numbers. for example:
Article Nr. | Title | Price | New Article
1001 | Test 1 | 10.0 | 1003
1002 | Test 2 | 20.0 |
1003 | Test 3 | 13.0 | ( = new Version of Test 1)
User should be able to search in all article numbers to get the title & price. If he types the "old" number 1001 he should get the title & price of the new version: 1003. (But this is only an easy example, its not limited how many articles are between the searched articlenr and the final articlenr)
Before I script a complex solution in php, I would like to ask if there is an "easier" way to get the correct article with a special database select?
--
Just thinking if the following (summarized) php code could be much easier than the requested mysql query:
function searcharticle(search_nr){
SELECT nr, title, price, newnr FROM articles WHERE nr = '$search_nr';
if(empty($newnr)){ $title = ... }else{ searcharticle($newnr); }
}
what do you think?
This should help at least by getting up to 4 levels deep on hierarchy of renewed items...
select
Articles.*
from
( select
IF( a4.ArticleNr is not null, a4.ArticleNr,
IF( a3.ArticleNr is not null, a3.ArticleNr,
IF( a2.ArticleNr is not null, a2.ArticleNr, A1.ArticleNr ))) as FinalArticle
from
Articles a1
left join Articles a2
on a1.NewArticle = a2.ArticleNr
left join Articles a3
on a2.NewArticle = a3.ArticleNr
left join Articles a4
on a3.NewArticle = a4.ArticleNr
where
a1.ArticleNr = '1001' ) QualifiedArticle
JOIN Articles
ON QualifiedArticle.FinalArticle = Articles.ArticleNr
The nested IF( IF( IF( ))) will start with the 4th generation deep "newest" version article... if found, it gets that, otherwise, gets 3rd newest, to 2nd newest to the single article the user actually provided. Since this is a self-join, it will only return a single record and be quite fast. From that, it joins the one final record with the articles table on THAT MATCHED find to get whatever the "newest" entry would be.
Managing hierarchical data in mysql is not an easy task, but it is possible.
Assuming articles table has article_num, Title, Price, new_article columns, all of them of VARCHAR type, with new_article as empty string for newest versions, this should do the trick of finding the Title and Price of newest version of given article:
SELECT a.Title, a.Price FROM articles a
JOIN
( SELECT
#article_num as prev_version,
#article_num :=
( SELECT new_article
FROM articles
WHERE article_num = prev_version ) AS article_num
FROM
( SELECT #article_num := '1001' ) AS starting_num,
articles
WHERE #article_num <> '' ) newer_articles
ON newer_articles.prev_version = a.article_num
AND newer_articles.article_num = ''
Regarding your php solution - yes, recursive approach in a language that allows it is a lot easier to code for adjacency list, also easier to read and maintain and performance should not be a problem as there won't be that many new versions of any article.

Hierarchical configuration select

Edit: Sorry for the confusion. Just got the OK from my boss to post this portion of the schema. I would have had more detail in the original post if I were allowed to post an image.
I have a configuration schema that looks like this:
http://img717.imageshack.us/img717/7297/heirarchy.png
Each of the levels is contained within the level below it (i.e. - a partner has multiple programs), and each config level shares config keys with the other types of config levels (i.e. - A default timezone can be set at the partner level, and then be overridden from the program, portfolio or device level).
What this allows us to do is have a default for a type of object, and then override that with more specific taxonomies. For instance:
Say I have a partner object that is a company. Say that hierarchy_configuration_key 1 is the default timezone. I put a partner_configuration that says that most often, that partner will be located on the east coast (NYC time).
Now I have multiple programs that that partner supports. Say that specific program is based out of California. I put a program_configuration that says that that devices in that program are Sacramento time.
Now let's skip portfolio, and say that someone signed up for this program based out of California moves to Denver but is still a customer. We set a Device configuration that says they're in Mountain time now.
The hierarchy looks like this:
Level |Timezone (hierarchy_configuration_key 1)
---------------------------------------------------
Partner |NYC
Program |Sacramento
Portfolio |null (defaults to most granular above it, so Sacramento)
Device |Denver
Now I want to select my configurations grouped by hierarchy_configuration_key_id:
I can use inner joins to traverse the levels, but I want a select to give me a result like this (grouped by hierarchy_configuration_key_id) for the primary key of the device (device_id):
device_id |portfolio_id |program_id |partner_id |device_config |portfolio_config |program_config| partner_config
---------------------------------------------------------------------------------------------------------------------
1 |2 |1 |35 |Denver |null |Sacramento | NYC
Also acceptable would be a Select that just gave me the most relevant config value, i.e.:
device_id |portfolio_id |program_id |partner_id |config_value
-------------------------------------------------------------
1 |2 |1 |35 |Denver
Thanks in advance. Let me know if you need any more clarification.
I think the only part that doesn't work here is pointed out by #EugenRieck's comment...
- Which field tells the Miata it is a Child of Mazda?
I would change the structure slightly...
ENTITY_TABLE
entity_id | parent_entity_id | entity_name
1 NULL Vehicle
2 1 Car
3 2 Mazda
4 3 Miata
5 1 Cycle
6 5 Unicycle
7 6 Broken Unicycle
PROPERTY_TABLE
entity_id | property_type | value
1 Wheels 4
2 Wheels NULL
3 Wheels NULL
4 Wheels NULL
5 Wheels 2
6 Wheels 1
7 Wheels 0
(And repeated for other property types as appropriate)
-- Every entity must have the same properties as the parents
-- (otherwise you have to find the topmost parent first to know what properties exist)
-- An entity may only have 1 parent
-- The topmost parent must have a NULL parent_id
-- The bottommost parent must be no more than 3 joins away from the topmost parent
Then you can have something like this...
SELECT
entity1.id,
property1.property_type,
entity1.name,
entity2.name,
entity3.name,
entity4.name,
property1.value,
property2.value,
property3.value,
property4.value,
COALESCE(property1.value, property2.value, property3.value, property4.value) AS inherited_value
FROM
entity AS entity1
LEFT JOIN
entity AS entity2
ON entity2.id = entity1.parent_id
LEFT JOIN
entity AS entity3
ON entity3.id = entity2.parent_id
LEFT JOIN
entity AS entity4
ON entity4.id = entity3.parent_id
INNER JOIN
property AS property1
ON property1.entity_id = entity1.id
LEFT JOIN
property AS property2
ON property2.entity_id = entity2.id
AND property2.property_type = property1.property_type
LEFT JOIN
property AS property3
ON property3.entity_id = entity3.id
AND property3.property_type = property1.property_type
LEFT JOIN
property AS property4
ON property4.entity_id = entity4.id
AND property4.property_type = property1.property_type
WHERE
entity1.id = #entity_id
AND property1.property_type = #property_type
This solution is based on your schema with #param1 being the hierarchy_configuration_key_id and #param2 being the desired device_id. It uses a method similar to Dems' although it was arrived at independently except for my borrowing of COALESCE.
SELECT *,
IF(dv_key IS NOT NULL,'device',IF(pf_key IS NOT NULL,'portfolio',IF(pg_key IS NOT NULL,'program',IF(pt_key IS NOT NULL,'partner',NULL)))) AS hierarchy_level,
COALESCE(dv_key,pf_key,pg_key,pt_key) AS key_id,
COALESCE(dv_value,pf_value,pg_value,pt_value) AS value
FROM
(SELECT sim_id,
dv.device_id, pt.partner_id, pg.program_id, pf.portfolio_id,
dvc.hierarchy_configuration_key_id AS dv_key, dvc.configuration_value AS dv_value,
pfc.hierarchy_configuration_key_id AS pf_key, pfc.configuration_value AS pf_value,
pgc.hierarchy_configuration_key_id AS pg_key, pgc.configuration_value AS pg_value,
ptc.hierarchy_configuration_key_id AS pt_key, ptc.configuration_value AS pt_value
FROM device dv
LEFT JOIN portfolio pf USING(portfolio_id)
LEFT JOIN program pg USING(program_id)
LEFT JOIN partner pt USING(partner_id)
LEFT JOIN device_configuration dvc ON dv.device_id=dvc.device_id AND dvc.hierarchy_configuration_key_id=#param2 AND dvc.active='true'
LEFT JOIN portfolio_configuration pfc ON pf.portfolio_id=pfc.portfolio_id AND pfc.hierarchy_configuration_key_id=#param2 AND pfc.active='true'
LEFT JOIN program_configuration pgc ON pg.program_id=pgc.program_id AND pgc.hierarchy_configuration_key_id=#param2 AND pgc.active='true'
LEFT JOIN partner_configuration ptc ON pt.partner_id=ptc.partner_id AND ptc.hierarchy_configuration_key_id=#param2 AND ptc.active='true'
WHERE dv.device_id = #param1) hierchy;