Hierarchical configuration select - mysql

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;

Related

MySQL Multi level nested CASE WHEN THEN ELSE with priority?

I am trying to wrap my mind around this little problem I've run into.
I am joining multiple tables containing vehicle information (so make, model, type, subtype, part numbers and part titles) in order to go through my "title override" table that might (or might not) contain entries where certain vehicle parts would have their title overwritten in certain cases, for certain vehicle makes, models, types or subtypes.
As in, think of using standard oil for all BMW cars, and then for BMW M5 there is an exception that overrides that standard oil with another premium oil. Or say standard oil is being used for all cars UNLESS otherwise specified in this table. And the priority goes down the list:
specific model (highest priority)
specific make (2nd highest priority)
subtype (3rd highest priority)
So the override table ("ov") looks something like this:
id | type_id | make_id | subtype_id | model_id | part_id | part_override
1 | 2 | 0 | 0 | 0 | 33 | Global-Replacement-Oil
2 | 2 | 1 | 0 | 0 | 33 | Yamaha-Premium-Oil
3 | 2 | 0 | 2 | 0 | 33 | Global-Dirtbike-Oil
4 | 2 | 1 | 4 | 0 | 33 | Yamaha-Streetbike-Oil
5 | 2 | 0 | 0 | 199 | 33 | Yamaha-R6-2015-Oil
** type_id = 2 is for motorcycles, type_id = 3 is for cars, for example
** if make_id, subtype_id or model_id are = 0 that means that override should apply to all makes, subtypes and models, unless value <> 0 is specified.
And for that part_id = 33, I only need to display it once in my results, not multiple times, depending on which model is queried in the application and where it fits in that "matrix".
So if I am running this query on Yamaha R6 2015 edition and need to pull the code/title for that part_id=33 for that model, "model_id" column has the highest priority over all the other ones, so I'll ignore all the other ones and show title for record #5.
If I however pick another Yamaha Street bike, the highest priority match I'd get would be record #4 (as it's got both make_id and subtype_id defined).
Or if I pick a random Dirtbike, I'll get a match on record #3 (on subtype_id), so that code/title will display instead.
And then if I pick any Yamaha bike, I'll get a match on record #2 (on make_id).
And at last, if none of those are specified and I just pick a random bike, it will show record #1 instead, as that will make a match on type_id alone (bikes = 2).
This is the query that I have so far (or part of it):
SELECT DISTINCT
CASE
WHEN p.part_id = '72' AND ov.part_override != "" AND ov.type_id = p.type_id AND
(CASE
WHEN ov.model_id IS NOT NULL
THEN ov.model_id = "$MODEL_ID"
ELSE
CASE
WHEN ov.make_id != 0
THEN ov.make_id = m.make_id
ELSE
CASE
WHEN ov.subtype_id != 0
THEN ov.subtype_id = st.subtype_id
ELSE 1
END
END
END)
THEN ov.part_override
ELSE p.part_number
END part_number,
ov.make_id,
....
FROM parts p ON ...
INNER JOIN makes m ON p.make_id = m.make_id
INNER JOIN ...
(just joining a bunch of other separate tables that contain all types,
subtypes, models and so on, respectively - irrelevant for the logic above)
What I am getting with this query is all the parts that apply to a specific model listed, so if I run it for a specific model like Yamaha R6 2015 I'll get the records #1,2 and 5 come up. But I just need record #5 to show up, as explained above, in the order of importance and priority.
If I do a GROUP BY part_id or something like that then I only get one record showing, but it's not necessarily the right one, in terms of highest priority.
What am I missing?
Or how can I cascade this whole importance/priority check down that query, so that while going through all the parts records in other joined tables and filtering it based on this override ("ov") table it only spits out the highest priority record, based on some sort of a waterfall or cascade rule?
Not sure how to write that, or if it's even possible.
Or if I have to run it as a recurring stored procedure of sorts, that's fine too.
Thanks!
Without seeing more accurately your other table joins, nor the actual table name you have here, you might want something with multiple left-joins to the override table, then pull COALESCE based on the first FOUND value. Something like..
select
...
coalesce( P_OV.part_override, M_OV.part_override,
MOD_OV.part_override, P.PartName ) as FinalPartName
from
Parts P
left join PartsOverride P_OV
on p.id = P_OV.part_id
join make m
on p.make_id = m.make_id
left join PartsOverride M_OV
on m.make_id = M_OV.make_id
join model mo
on p.model_id = mo.model_id
left join PartsOverride MOD_OV
on m.make_id = MOD_OV.make_id
Then, you can change the order of priority of which "OV" version you want within the coalesce(). So if Model is higher priority than make:
coalesce( MOD_OV.part_override, M_OV.part_override,
P_OV.part_override, P.PartName ) as FinalPartName
Whichever field is NOT NULL FIRST WINS.

MySQL Query which require multiple joins

I have a system that is used to log kids' their behavior. If a child is naughty it is logged as negative and if it has a well behaviour it is logged as positive.
For instance - if a child is rude it gets a 'Rude' negative and this is logged in the system with minus x points.
My structure can be seen in this sqlfiddle - http://sqlfiddle.com/#!9/46904
In the users_rewards_logged table, the reward_id column is a foreign key linked to either the deductions OR achievements table depending on the type of column.
If type is 1 is a deduction reward, if the type value is 2 is a achievement reward.
I basically want a query to list out something like this:
+------------------------------+
| reward | points | count |
+------------------------------+
| Good Work | 100 | 1 |
| Rude | -50 | 2 |
+------------------------------+
So it tallys up the figures and matches the reward depending on type (1 is a deduction, 2 is a achievement)
What is a good way to do this, based on the sqlfiddle?
Here's a query that gets the above desired results:
SELECT COALESCE(ua.name, ud.name) AS reward,
SUM(url.points) AS points, COUNT(url.logged_id) AS count
FROM users_rewards_logged url
LEFT JOIN users_deductions ud
ON ud.deduction_id = url.reward_id
AND url.type = 1
LEFT JOIN users_achievements ua
ON ua.achievement_id = url.reward_id
AND url.type = 2
GROUP BY url.reward_id, url.type
Your SQLFiddle had the order of points and type in the wrong order for the table users_rewards_logged.
Here's the fixed SQLFiddle with the result:
reward points count
Good Work 100 1
Rude -50 2
Although eggyal is correct--this is rather bad design for your data--what you ask can be done, but requires a UNION clause:
SELECT users_achievements.name, users_rewards_logged.points, COUNT(*)
FROM users_rewards_logged
INNER JOIN users_achievements ON users_achievements.achievement_id = users_rewards_logged.reward_id
WHERE users_rewards_logged.type = 2
UNION
SELECT users_deductions.name, users_rewards_logged.points, COUNT(*)
FROM users_rewards_logged
INNER JOIN users_deductions ON users_deductions.deduction_id = users_rewards_logged.reward_id
WHERE users_rewards_logged.type = 1
GROUP BY 1, 2
There's no reason NOT to combine the achievements and deductions tables and just use non-conflicting codes. If you combined the tables, then you would no longer need the UNION clause--your query would be MUCH simpler.
I noticed that you have two tables (users_deductions and users_achievements) that defines the type of reward. As #eggyal stated, you are violating the principle of orthogonal design, which causes the lack of normalization of your schema.
So, I have combined the tables users_deductions and users_achievements in one table called reward_type.
The result is in this fiddle: http://sqlfiddle.com/#!9/813d5/6

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.

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

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!

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