Hierarchical result in MySQL - mysql

I have two tables:
+------------------------------------------------------+
| HIERARCHICAL_RECORDS |
+----------------+--------------------+----------------+
| ORG_ID | NAME | VALUE |
+----------------+--------------------+----------------+
| 333 | CC | ... |
| 22 | MT | ... |
| 22 | TMP | ... |
| 333 | TMP | ... |
+------------------------------------------------------+
and a second one with ORGs hierarchy:
+---------------------------------+
| ORGANIZATION |
+----------------+----------------+
| ORG_ID | PARENT_ID |
+----------------+----------------+
| 1 | null |
| 22 | 1 |
| 333 | 22 |
+---------------------------------+
which represent an hierarchy of parameters' values on UI:
Org ID# 1
|
|----- Org ID# 22
[MT -> value]
[TMP -> value]
|
|----- Org ID# 333
[CC -> value]
[MT -> shows value defined in parent #22]
[TMP -> redefined value]
Here's the thing: if both parent and child have defined some attribute value (e.g. TMP in example), we always should return to the client the value redefined by a child.
So, I would like to have a query that by a given child ID (or maybe set of all the IDs from child to root) will return me records that are defined on the very last level.
E.g., for the example above, if I pass 333 (or set of 1, 22, 333), I would like to have a result of:
+----------------+--------------------+----------------+
| ORG_ID | NAME | VALUE |
+----------------+--------------------+----------------+
| 333 | CC | ... |
| 22 | MT | ... |
| 333 | TMP | ... |
+------------------------------------------------------+
where TMP's value from child ORG# 333 will hide one from parent ORG# 22.
So, I need to somehow filter records for parent ORGs and leave only ones actual for child ORGs. At the same time, if a value isn't redefined at a child level, we take the value from it's parent (as with MT defined at ORG# 22 but not redefined at ORG# 333).
How can I do that?
Many thanks!

This should be a comment, but its a bit long. Since mysql has no "connect by" operator you will struggle to do this in mysql. The only practical solution would be to write a recursive procedure which would populate a temporary table. But it won't be very efficient. Indeed one solution, depending on how frequently the data changes would be to apply the mapping to all the nodes in the database. Whether other solutions (the graph engine, geospatial indexing or alternate schemas) might be more appropriate would depend on the frequency of changes the number of records, the distribution and the cardinality of the data (which you haven't mentioned).

Related

FDQuery and OnCalcFields, get the previous line

Delphi 10.3.3
FireDAC: DBGrid / FDQuery / MySQL
VCL
Hi all,
I have a table with these fields
----------------------
| id | data |
----------------------
| 1 | 0=A;1=B;2=C |
| 2 | 2=Z |
| 3 | |
| 4 | 0=Y;1=X |
| 5 | |
| 6 | |
Each row of data represents only the change in the table
I would like this to be display in a DBGRID:
-----------------------
| id | C0 | C1 | C2 |
-----------------------
| 1 | A | B | C |
| 2 | A | B | Z |
| 3 | A | B | Z |
| 4 | Y | X | Z |
| 5 | Y | X | Z |
| 6 | Y | X | Z |
What I can do for now is only the following table:
-----------------------
| id | C0 | C1 | C2 |
-----------------------
| 1 | A | B | C |
| 2 | | | Z |
| 3 | | | |
| 4 | Y | X | |
| 5 | | | |
| 6 | | | |
To obtain this result, I create additional columns in the event FDQuery1.BeforeOpen
And in the event OnCreateFields, I fill each column but I don't know the previous row content,
So, how can I do to fill in the missing fields in the DBgrid?
Thanks
Franck
I think you mean OnCalcFields, rather than OnCreateFields.
What you need
is certainly possible, either server-side by deriving the necessary values from the prior
row using e.g. a SQL subquery or client-side using calculated fields. This answer is about doing it
client-side.
The problem with doing client-side calculations involving another dataset row is that
to do this you need to be able to move the dataset cursor during the OnCalcFields event. However, at the time, the DataSet will be in either dsCalcFields or dsInternalCalc state
and, while it is, you can't easily move to another row in the dataset. It is possible to do this, but
requires declaring a descendant dataset class (TMyFDQuery) so that you can access the SetTempState
necessary to do revert to the prior state after you've picked up the necessary info from the "other"
row and, if what you need involves more that one field, you need somewhere to store the values temporarily.
So doing it that way gets messy.
A much cleaner approach involves using functional similarity between FireDAC's datasets and TClientDataSets.
One of the nice features of TClientDatasSets is the ease with which you can move the dataset contents between
two CDSs simply by doing
CDS2.Data := CDS1.Data;
FireDAC datasets can do the same trick, but between any FD dataset types. So here is what I would do in your
situation:
Add an FDMemTable to your form/datamodule and copy the query data into it in the FDQuery's AfterOpen event like
this:
procedure TForm2.FDQuery1AfterOpen(DataSet: TDataSet);
begin
FDQuery1.DisableControls;
try
FDMemTable1.Data := FDQuery1.Data;
FDMemTable1.Open;
finally
FDQuery1.First;
FDQuery1.EnableControls;
end;
end;
The FDQuery1.First is to force it to re-do its calculated fields once the FDMemTable data is available
(during the initial FDQuery1.Open, it can't be, of course).
In the FDQuery's OnCalcFields event, use code like this to base the calculated fields'
values on values picked up from the prior row (if there is one of course, the first
row can't hae a "prior" row):
procedure TForm2.FDQuery1CalcFields(DataSet: TDataSet);
begin
if FDMemTable1.Active then begin
if FDMemTable1.Locate('ContactID', FDQuery1.FieldByName('ContactID').AsInteger, []) then begin
FDMemTable1.Prior;
if not FDMemTable1.Bof then begin
// Set FDQuery1's calculated fields that depend on prior row
FDQuery1.FieldByName('PriorRowID').AsInteger := FDMemTable1.FieldByName('ContactID').AsInteger;
end;
end;
end;
end;
In this example, my queried dataset has a ContactID primary key and the calculated value is simply the ContactID value from the prior row. In real life, of course, it
would be more efficient to use persistent field variables rather than keep calling FieldByName.
I suppose another possibility might be to use the CloneCursor method to obtain a lookup cursor
to access the "prior" row, but I've not tried that myself and it may not be possible anyway
(what happens about the calculated fields in the CloneCuror copy?).

mysql table having a->b and b->a values, select only a->b set of values

I have one table having 5 columns
linkid, orinodeno, orinodeno, ternodeno, terifindex
linkid is autoincremented. orinodeno, oriifindex is one combination value and ternodeno, terifindex other combination (orinodeno,oriifindex is originating value and ternodeno,terifindex terminating value i.e, in between there is a link eg just like map two pts n in between connecting link) so my table contains a->b values (i.e a is combination of orinodeno, oriifindex and b is combination of ternodeno,terifindex) and b->a values. so I have to select only a->b set of values not b->a. Also sending my table image. My Table
There is no a map definition in sql databases, forget it. Check any database normalization tutorial. Then you shouldn't have any problems with select statements.
Please be clear about what you are asking. If you can not explain in words, please give example input and your expected output.
From link of table image you have provided and description, It looks like you expect following:
Data in current table:
------------------------------------------------------------------
|linkid | orinodenumber | oriifindex | ternodenumber | terifindex|
------------------------------------------------------------------
|305 | 261 | 2 | 309 | 2 |
|306 | 309 | 2 | 261 | 2 |
|307 | 257 | 10 | 310 | 10 |
|308 | 310 | 10 | 257 | 10 |
|309 | 257 | 11 | 310 | 11 |
------------------------------------------------------------------
Expected Output:
------------------------------------------------------------------
|linkid | orinodenumber | oriifindex | ternodenumber | terifindex|
------------------------------------------------------------------
|305 | 261 | 2 | 309 | 2 |
|307 | 257 | 10 | 310 | 10 |
------------------------------------------------------------------
If that is your case, following query might help you (Assuming table name as link_table):
SELECT *
FROM link_table o
WHERE EXISTS (SELECT linkid
FROM link_table i
WHERE o.orinodenumber = i.ternodenumber
AND o.oriifindex = i.terifindex
AND o.linkid < i.linkid);

Moving some data from one column of MySQL table to another

I was wondering if there is an easy way of moving some (not all) data from one column to another.
My MySQL table has 200 entries but this is the simplified version of what I am trying to do:
| ID | A | B |
| 1 | | |
| 2 | | |
| 3 | | aa|
| 4 | | bb|
| 5 | | cc|
So I need to get data from column B to Column A but only the ones that have ID greater than (>) 2. so that aa from 3B will go to 3A, bb from 4B will go to 4A...
UPDATE <tablename> SET
A=B,
-- B=''
WHERE ID>2
Might help. The commented-out line needs to be enabled or disabled, depending on whether you want to move or copy the values between columns.

How to replace substring in mysql where string is based on other table-column values

I have two mysql tables as
Component
+----+-------------------------+--------+
| OldComponentId | NewComponentId |
+----+-------------------------+--------+
| 15 | 85 |
| 16 | 86 |
| 17 | 87 |
+----+-------------------------+--------+
Formulae
+----+-------------------------+--------+
| id | formula_string |
+----+-------------------------+--------+
| 1 | A+15-16+17 |
| 2 | 16+15-17 |
+----+-------------------------+--------+
I want to replace value of formula_string on the basis of NewComponentId as
Formulae
+----+-------------------------+--------+
| id | formula_string |
+----+-------------------------+--------+
| 1 | A+85-86+87 |
| 2 | 86+85-87 |
+----+-------------------------+--------+
I have tried with following mysql query but its not working
update Formulae fr, Component comp set formula_string=REPLACE(fr.formula_string,comp.OldComponentId,comp.NewComponentId).
Please suggest the solutions
thanks.
There is no easy way to do this. As you observed in your update statement, the replacements don't nest. They just replace one at a time.
One thing that you can do is:
update Formulae fr cross join
Component comp
set formula_string = REPLACE(fr.formula_string, comp.OldComponentId, comp.NewComponentId)
where formula_string like concat('%', comp.OldComponentId, '%')
Then continue running this until row_count() returns 0.
Do note that your structure could result in infinite loops (if A --> B and B --> A). You also have a problem of "confusion" so 10 would be replaced in 100. This suggests that your overall data structure may not be correct. Perhaps you should break up the formula into separate pieces. If they are just numbers and + and -, you can have a junction table with the value and the sign for each component. Then your query would be much easier.

How to split CSVs from one column to rows in a new table in MSSQL 2008 R2

Imagine the following (very bad) table design in MSSQL2008R2:
Table "Posts":
| Id (PK, int) | DatasourceId (PK, int) | QuotedPostIds (nvarchar(255)) | [...]
| 1 | 1 | | [...]
| 2 | 1 | 1 | [...]
| 2 | 2 | 1 | [...]
[...]
| 102322 | 2 | 123;45345;4356;76757 | [...]
So, the column QuotedPostIds contains a semicolon-separated list of self-referencing PostIds (Kids, don't do that at home!). Since this design is ugly as a hell, I'd like to extract the values from the QuotedPostIds table to a new n:m relationship table like this:
Desired new table "QuotedPosts":
| QuotingPostId (int) | QuotedPostId (int) | DatasourceId (int) |
| 2 | 1 | 1 |
| 2 | 1 | 2 |
[...]
| 102322 | 123 | 2 |
| 102322 | 45345 | 2 |
| 102322 | 4356 | 2 |
| 102322 | 76757 | 2 |
The primary key for this table could either be a combination of QuotingPostId, QuotedPostId and DatasourceID or an additional artificial key generated by the database.
It is worth noticing that the current Posts table contains about 6,300,000 rows but only about 285,000 of those have a value set in the QuotedPostIds column. Therefore, it might be a good idea to pre-filter those rows. In any case, I'd like to perform the normalization using internal MSSQL functionality only, if possible.
I already read other posts regarding this topic which mostly dealt with split functions but neither could I find out how exactly to create the new table and also copying the appropriate value from the Datasource column, nor how to filter the rows to touch accordingly.
Thank you!
€dit: I thought it through and finally solved the problem using an external C# program instead of internal MSSQL functionality. Since it seems that it could have been done using Mikael Eriksson's suggestion, I will mark his post as an answer.
From comments you say you have a string split function that you you don't know how to use with a table.
The answer is to use cross apply something like this.
select P.Id,
S.Value
from Posts as P
cross apply dbo.Split(';', P.QuotedPostIds) as S