Delphi 2009, MyDAC and relational database - mysql

I have quite a problem concerning the use of relational database concepts in Delphi 2009 with MyDAC.
I have a database structure that looks somehow like the following:
Item
id
name
Storage
id
name
StorageItem
id
item_id
storage_id
place
Now when I have an active dataset from "Item" how can I display all associated Storages in for example a DBGrid?
By the way: Would it be better to not use "id" in every table but to alter it and use something like for example "id_item" or "id_storage"?
Thank you in advance :)

With StorageItem you created a
many-to-many relationship. If you
need just one-to-many (many storages
are related to one item, but you
don't need the vice versa), then you
may just add another field to the
Storage table (item_id) that would
be a foreign key for Items table.
Then you create an index on
item_id in Storage table, and
connect the two tables in
master-detail relationship.
If you do need many-to-many then you
may add a query component with SQL
(select * from StorageItem where
item_id := :current_storage_id), and
current_storage_id is your query's
parameter.

Select a.ID, b.Name, a.Place
from StorageItem a
inner join Storage b
on (a.id = b.id)
the above query will return all the items in StorageItem table with it's name, now if you want to filter it to return only items for a specific item add where clause to be like
Select a.ID, b.Name, a.Place
from StorageItem a
inner join Storage b
on (a.id = b.id)
where a.item_id = 1 -- place the item id here
you can use where with parameters such as:
MyQuery.Sql.Text := ' Select a.ID, b.Name, a.Place from StorageItem a
+ ' inner join Storage b on (a.id = b.id) '
+ ' where a.item_id = :ItemNo ';
MyQuery.ParamByName('ItemNo').asInteger := 1;
MyQuery.Open;
and assign the query above to dbGrid
also you can use MasterSource property to make the relations without using the "where" part

I'm not familiar with MyDAC personally, but most dataset components have some way to establish master-detail relationships. Check if there's a MasterSource property on your dataset, or some similar way to link a detail dataset to a master dataset. If not, you could use a TDatasetField to establish a link, and filter the nested dataset to only display the right records.
As for ID column names, it's a good idea to give a descriptive name to each field, so you can tell by looking at the code that you've got your links right. If you call your id column "id", that could be any id column, and that could get confusing if you start passing around references to datasets. But if it's called item_id every time, (not item_id sometimes and id_item sometimes) then you always know exactly what you're looking at. It makes it easier to know that your code is right, too. A filter that says "master.item_id = detail.item_id" is easier to read that "master.id = detail.item_id". That could be wrong and fail silently if master is assigned to the wrong dataset, for example.

Related

sql IN reverse usage

I am trying to show products belongs to that location but I failed.
db_products.locationId stores id's string like 1,2,5,9
:locationId stores location id like 5 I send this variable as parameter to sql query.
SELECT db_products.*,
db_assets.path
FROM db_products INNER JOIN
db_assets ON db_assets.guid = db_products.guid
WHERE db_products.pcId = 1 AND
:locationId IN (db_products.locationId)
ORDER BY db_products.id
Fix your data structure! Do not store integers as strings in a list. Why not?
Integers should be stored as numbers, not strings.
SQL has poor string handling.
Foreign key relationships should be properly declared.
The resulting queries cannot make use of indexes.
SQL has a great data structure for storing lists. It is called "table".
So, use a junction table. You can look it up.
Sometimes, we are stuck with other people's really bad design decisions. In this case, MySQL has find_in_set():
SELECT p.*, a.path
FROM db_products p INNER JOIN
db_assets a
ON a.guid = p.guid
WHERE p.pcId = 1 AND
FIND_IN_SET(:locationId, p.locationId) > 0
ORDER BY p.id;

Fetching value from table and passing it in url

How can i use in table field values in the url
SQL Query wherein all 3 tables are joined
select * from nfojm_usedcar_variants cv
inner join nfojm_usedcar_products cp
inner join nfojm_usedcar_categories cc on
cc.id=cp.prod_cat_id and
cp.id=cv.v_prod_id and
cv.state='1' order by cv.id desc
Output as checked
Then it combines all 3 tables
nfojm_usedcar_variants
nfojm_usedcar_products
nfojm_usedcar_categories
However - all 3 tables have unique field i.e id (but with different values)
I need to pass on value of id and v_prod_id in a url
say url been :-
<a href="index.php?option=com_usedcar&pid='.$row->v_prod_id.'&vid='.$row->id.'">
But id been common field in most of the tables hence its not picking in correctly from nfojm_usedcar_variants,
Can some one help to modify a function so as to fetch in value of id and v_prod_id from the respective table of nfojm_usedcar_variants
thanks
If you have multiple tables in a join that share a common column name, and you need them, then alias them. Such as:
select a.id as aid,a.theName,b.id as bid,b.year
from tableA a
join tableB b
on b.id=a.id
then refer to those columns as aid and bid in your code that follows.
Try to avoid Ever doing a select *. Be explicit. You never know what comes flying out of a select * typically. And odds are you don't need it all. Select * is fine for messing around, but not for production code. And you can't control common column names with select *. We like to control things afterall, no?

Renaming a Key on a multi-table MySQL SELECT

I'm trying to get the content of 3 different tables.
table A = Is containing our users list, table B = Is returning contracts related to users,table C = Is returning formula details related to the contracts.
In order to make it the right way, I'm using the following multi table request:
SELECT * FROM rscm_students A, rscm_files B, rscm_formulas C
WHERE B.dossier_status = 0
AND A.student_agency = :agency
AND B.file_student_id = A.id
AND B.file_formula_id = C.id
AND C.formula_place = 0
GROUP BY A.student_uniqid
ORDER BY B.file_date_create";
This is where the whole damn thing become a little complicated. It is returning the correct datas, but as the primary key of every table here is called "id". I can't do some foreach in php. If I got 3 contracts on 1 user, it impossible for me to regroup every contract in the same user array.
I'm still not an expert in SQL, that's why I'm using Phinx to control my database. This is also why my primary keys are named "id".
If you have a good idea, please let me know!
Alright, I will make an answer out of it.
First off, don't use
select *
The above select is fine for quick and dirty development prior to production. But it makes a mess out of things such as your joins with common column names coming out of multiple tables (like id and others).
Use modern explicit join syntax. Don't use the older join style. So use join and on.
Lastly with table aliases, create unique output column names for the id columns or other clashes such as
A.id as aid, B.id as bid

Multiple data stored into a field for better management?

I have a MySQL table as follow:
id, user, culprit, reason, status, ts_register, ts_update
I was thinking of using the reason as an int field and store just the id of the reason that could be selected by the user and the reason itself could be increased by the admin.
What I meant by increased is that the admin could register new reason, for example currently we have:
Flood, Racism, Hacks, Other
But the admin could add a new reason for instance:
Refund
Now my problem is that I would like to allow my users to select multiple reasons, for example:
The report 01 have the reasons Flood and Hack.
How should I store the reason field so that I could select multiple reasons while maintaining a good table format?
Should I just go ahead and store it as a string and cast it as an INT when I am searching thru it or there are better forms to store it?
UPDATE Based on Jonathan's reply:
SELECT mt.*, group_concat(r.reason separator ', ') AS reason
FROM MainTable AS mt
JOIN MainReason AS mr ON mt.id = mr.maintable_ID
JOIN Reasons AS r ON mr.reason = r.reason_id
GROUP BY mt.id
The normalized solution is to have a second table containing one row for each reason:
CREATE TABLE MainReasons
(
MainTable_ID INTEGER NOT NULL REFERENCES MainTable(ID),
Reason INTEGER NOT NULL REFERENCES Reasons(ID),
PRIMARY KEY(MainTable_ID, Reason)
);
(Assuming your main table is called MainTable and you have a table defining valid reason codes called Reasons.)
From a comment:
[W]ould you be [so] kind [as] to show me an example of selecting something to retrieve a report's reason? I mean if I simple select it SELECT * FROM MainTABLE I would never get any reasons since MainTable doesnt know it right? Because it is only linked to the MainReasons and Reasons table so I would need to do something like SELECT * FROM MainTable LEFT JOIN MainReasons USING (MainTable_ID) or something alike but how would I go about getting all the reasons if multiples?
SELECT mt.*, r.reason
FROM MainTable AS mt
JOIN MainReason AS mr ON mt.id = mr.maintable_ID
JOIN Reasons AS r ON mr.reason = r.reason_id
This will return one row per reason - so it would return multiple rows for a single report (recorded in what I called MainTable). I omitted the reason ID number from the results - you can include it if you wish.
You can add criteria to the query, adding terms to a WHERE clause. If you want to see the reports where a specific reason is specified:
SELECT mt.*
FROM MainTable AS mt
JOIN MainReason AS mr ON mt.id = mr.maintable_ID
JOIN Reasons AS r ON mr.reason = r.reason_id
WHERE r.reason = 'Flood'
(You don't need the reason in the results - you know what it is.)
If you want to see the reports where 'Floods' and 'Hacks' were the reasons given, then you can write:
SELECT mt.*
FROM MainTable AS mt
JOIN (SELECT f.MainTable_ID
FROM (SELECT MainTable_ID
FROM MainReason AS mr1
JOIN Reasons AS r1 ON mr1.reason = r1.reason_ID
WHERE r1.reason = 'Floods'
) AS f ON f.MainTable_ID = mt.MainTable_ID
JOIN (SELECT f.MainTable_ID
FROM (SELECT MainTable_ID
FROM MainReason AS mr2
JOIN Reasons AS r2 ON mr2.reason = r2.reason_ID
WHERE r1.reason = 'Hacks'
) AS h ON h.MainTable_ID = mt.MainTable_ID
To do a one-to-many relationship, I would spin reason off into it's own table, like so:
id, parent_id, reason
parent_id would refer back into your current table's id.
You could store it as an INT, but it would be a continual pain to have to parse it every time you wanted to read the data. This way would just take one more join.

MySQL column definition

Is there a way, using MySQL 5.0, to define a column in a table that is to be calculated whenever a select is executed on that particular row? For example say I have two tables, A and B:
A:
ID
COMPUTED_FIELD = SUM(SELECT B.SOME_VALUE FROM B WHERE B.A_ID = ID)
B:
ID
A_ID
SOME_VALUE
In the example, when I run a select on A for a particular ID, I want it to return the sum of all values in Table B for that particular value of A.ID. I know how to do this using multiple separate queries and doing a group by A_ID, but I'm trying to streamline the process a little.
Yes. You cannot do that inside a table, but you can do it in a view.
CREATE VIEW A
AS
SELECT SUM(B.SOME_VALUE) AS COMPUTED_FIELD
FROM B
WHERE B.A_ID = 'id';
Obviously id needs to be whatever you are searching for.
You don't need table A in this case.
Tables cannot contain calculated values. Try using views. The manual entry has full details. Your end result will come out looking something like: CREATE VIEW A AS SELECT SUM('SOME_VALUE'), B.A_ID FROM B; (Not tested)
That said, I'm not sure how important it is for you to have an independent, unique ID for table A -- this isn't possible unless you add another table C to hold the foreign keys referenced by B.A_ID, and use table C as a reference in creating your view.
As txwikinger suggests, the best way to do this is set up A as a view, not a table. Views are, for all intents and purposes, a streamlined, reusable query. They're generally used when a)a common query has a computed column, or b)to abstract away complex joins that are often used.
To expand on the previous answer, in order to be able to query A for any ID, try this view:
CREATE VIEW A
AS
SELECT B.A_ID AS ID, SUM(B.SOME_VALUE) AS COMPUTED_FIELD
FROM B
GROUP BY B.A_ID;
Then you can select into it normally, for example:
SELECT A.ID, A.COMPUTED_FIELD
FROM A
WHERE A.ID IN (10, 30);
or
SELECT A.ID, A.COMPUTED_FIELD
FROM A
WHERE COMPUTED_FIELD < 5;