Im trying to make some sort of localization in my DB.
For example I have 3 tables(img 1). Languages table contains different languages. Localization table has 3 fields: "id" - id of the string, 'language' - language of the string(id and language are my primary key), 'value' - localized string. tableOne has 'id', 'Col1' and 'Col2' - these fields contain IDs of the localizeable strings.
So after localizing I expect to get one of green tables instead of original(depending on a language parameter).
I've made it this way and it works, but I'd like to know is there any other better way to make it because now I have to create INNER JOIN block for each column, which must be localized. Im just scared that it will be very very slow.
I tried to create a temporary table to select all records of the required language and then i was doing the same. Inner joins, but searches should be performed only among the records of one language. But its not working because i still had to use multiple inner joins with that temp table which is impossible.
SELECT
`One`.`id` AS 'id',
`loc1`.`value` AS 'Col1',
`loc2`.`value` AS 'Col2'
FROM
`tableOne` AS `One`
INNER JOIN
`localization` AS `loc1`
ON `loc1`.`id` = `One.Col1`
AND `loc1`.`language` = 'en'
INNER JOIN
`localization` AS `loc2`
ON `loc2`.`id` = `One.Col2`
AND `loc2`.`language` = 'en'
img 1
If you want to reduce the number of JOINS needed, try displaying the values in rows instead of columns. You could do so like this:
SET #lang := 'en';
SELECT 1, tmp.value
FROM(
SELECT value
FROM localization
WHERE language = #lang AND id IN(543, 345)) tmp;
I first set a language parameter, and then I pull all values for that language from the localization table, using the ids inside an IN operator. You'll get results like this:
| 1 | one |
| 1 | two |
If you have to use the format given in the first table, try doing one inner join where you pull for the specific language and ids like this:
SELECT t1.id, t1.col1, t1.col2,
CASE WHEN l.id = t1.col1 THEN l.value ELSE null END AS col1Value,
CASE WHEN l.id = t1.col2 THEN l.value ELSE null END AS col2Value
FROM firstTable t1
JOIN localization l ON l.id IN (t1.col1, t1.col2) AND l.language = #lang;
Unfortunately, this won't give you the final solution, it will give you values like:
| 1 | 543 | 345 | one | null |
| 1 | 543 | 345 | null | two |
To wrap those into one column and remove nulls, just add MAX():
This will run a case statement for each column you have, but it will only have one JOIN and looks a little more manageable:
SELECT t1.id,
MAX(CASE WHEN l.id = t1.col1 THEN l.value ELSE null END) AS col1Value,
MAX(CASE WHEN l.id = t1.col2 THEN l.value ELSE null END) AS col2Value
FROM firstTable t1
JOIN localization l ON l.id IN (t1.col1, t1.col2) AND l.language = #lang
GROUP BY t1.id;
Here is an SQL Fiddle example. I don't think the case blocks will bog you down too much, but let me know how this preforms against your actual database.
Related
I have a problem with my SELECT query. I try to retrieve a tagValue name "CallsBy", but only if the value is "Entry point" - otherwise I want to compute the calls by another sub query.
I have 3 tables :
t_object: with all the classes
t_operation: with all the operations, link with t_object.Object_ID = t_operation.Object_ID
t_operationtag: with all the tag value for each operation, link with t_operation.OperationID = t_operationtag.ElementID
Here is a demo with my tables I have:
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=b6a1672948db816aefb23b8dc8d8e01d
The result for the class 1 would be :
Name Operation | CallsBy
-----------------+------------------------------------
class1Operation1 | class2Operation2, class3Operation1
class1Operation2 | class2Operation3
class1Operation3 | class2Operation3
The result for the class 2 would be :
Name Operation | CallsBy
-----------------+----------------------------------------------------
class2Operation1 | class1Operation1, class3Operation1,class3Operation3
class2Operation2 | Entry point of Tabidi
class2Operation3 | class1Operation1
The result for the class 3 would be :
Name Operation | CallsBy
-----------------+---------------------------------------
class3Operation1 | class1Operation2, class3Operation1
class3Operation2 | Entry point of Tabada
class3Operation3 | class2Operation1
Here is the sub query to compute the CallsBy if is not a "Entry point" :
select
t2.name, group_concat(t1.name)
from
t_operation t1
left join
(select to3.ElementID, to2.name
from t_object to1
left join t_operation to2 on to1.Object_ID = to2.Object_ID
left join t_operationtag to3 on find_in_set(to2.Name, to3.VALUE)
where to3.Property = 'Calls'
and to1.Object_ID = '1') t2 on t1.OperationID = t2.ElementID
where
t2.ElementID is not null
group by
t2.name;
I try to write the query but I didn't get any result...
First off let me say that I believe your database table structure and relationships might not be optimal, it seems complex for just 3 tables.
Also, I believe there is a difference between the data you listed in this post and the data that you listed in the db fiddle example. I created all of the tables and populated them with the data you gave and the results of the query I came up with don't exactly match the data you listed above as the expected result.
However, I believe this query will get you what you want: if the corresponding t_operationtag.Property = 'CallsBy' and the t_operationtag.VALUE contains Entry point then it returns the t_operationtag.VALUE else it group concats the t_operation.Name values for the given object id:
SELECT top.Name,
CASE WHEN topt2.VALUE IS NOT NULL THEN topt2.Value
ELSE GROUP_CONCAT(topt1.VALUE)
END AS CallsBy
FROM t_operation AS top
JOIN t_operationtag AS topt1 ON topt1.ElementID = top.OperationID
LEFT JOIN t_operationtag AS topt2 ON topt2.ElementID = top.OperationID
AND topt2.Property = 'CallsBy'
AND topt2.VALUE LIKE '%Entry point%'
JOIN t_object AS obj ON obj.Object_ID = top.Object_ID
WHERE obj.Object_ID = 1 -- or 2 or 3
GROUP BY top.Name, topt1.PropertyID, topt2.PropertyID
ORDER BY top.Name
I hope this helps.
I have a Profile table like this
|--------|-----------|
| People | Favorite |
|--------|-----------|
| A | Movie |
| B | Movie |
| B | Jogging |
|--------|-----------|
Q: How to retrieve the people whose favorite is movie but not jogging?
In this table, the result is only People A.
Although I came out with this
select People from Profile
where
People
in
(select People from Profile
where favorite='Movie')
and
People
not in
(select People from Profile
where favorite='Jogging')
But it seem like can be better, any suggestion or answer (without using join or union clause)?
https://www.db-fiddle.com/f/rboiDpxxbABCpjtduEz7uY/1
SELECT People
FROM `profile`
GROUP BY people
HAVING SUM('Movie' = favorite) > 0
AND SUM('Jogging' = favorite) = 0
There's lots of ways. While you can use a UNION, its rather messy and innefficient. MySQL doesn't have a MINUS clause which would give a fairly easy to understand query.
You could aggregate the data:
SELECT people
, MAX(IF(favorite='jogging', 1, 0)) as jogging
, MAX(IF(favorite='movie', 1, 0)) as movie
FROM profile
GROUP BY people
HAVING movie=1 AND jogging=0
Or use an outer join:
SELECT m.people
FROM profile m
LEFT JOIN
( SELECT j.people
FROM joggers j
WHERE j.favorite='jogging' ) joggers
ON m.people=joggers.people
WHERE joggers.people IS NULL
AND m.favorite='movies'
Using a NOT IN/NOT EXISTS gives clearer syntax but again would be very innefficient.
There are several query patterns that will return a result that satisfies the specification.
We can use NOT EXISTS with a correlated subquery:
SELECT p.people
FROM profile p
WHERE p.favorite = 'Movie'
AND NOT EXISTS ( SELECT 1
FROM profile q
WHERE q.favorite = 'Jogging'
AND q.people = p.people /* related to row in out query */
)
ORDER
BY p.people
An equivalent result can also be done with an anti-join pattern:
SELECT p.people
FROM profile p
LEFT
JOIN profile q
ON q.people = p.people
AND q.favorite = 'Jogging'
WHERE q.people IS NULL
AND p.favorite = 'Movie'
ORDER BY p.people
Another option is conditional aggregation. Without a guarantee about uniqueness, and some MySQL shorthand:
SELECT p.people
FROM profile p
GROUP
BY p.people
HAVING 1 = MAX(p.favorite='Movie')
AND 0 = MAX(p.favorite='Jogging')
A more portable more ANSI standard compliant syntax for the conditional aggregation:
SELECT p.people
FROM profile p
GROUP
BY p.people
HAVING 1 = MAX(CASE p.favorite WHEN 'Movie' THEN 1 ELSE 0 END)
AND 0 = MAX(CASE p.favorite WHEN Jogging' THEN 1 ELSE 0 END)
This is a common problem when you want to have multiple conditions with the same column. I have answered this here and there are other methods like intersect and subqueries.
SELECT people, GROUP_CONCAT(favorite) as fav
FROM profile
GROUP BY people
HAVING fav REGEXP 'Movie'
AND NOT fav REGEXP 'Jogging';
With group by people and checking the minimum and maximum values of favorite to be 'Movie':
select people from tablename
where favorite in ('Movie', 'Jogging')
group by people
having min(favorite) = 'Movie' and max(favorite) = 'Movie'
I have following query:
http://www.sqlfiddle.com/#!9/752e34/3
This query use SELECT in SELECT queries.
"SELECT a.*
,(SELECT s.value FROM tbl_scd AS s WHERE s.tag_id = 1 AND s.main_id = a.id ORDER BY s.date_time DESC LIMIT 1) AS title
,(SELECT s.value FROM tbl_scd AS s WHERE s.tag_id = 2 AND s.main_id = a.id ORDER BY s.date_time DESC LIMIT 1) AS alt
FROM tbl_main AS a
WHERE 1;"
Now I'm looking for a solution to add a new row into tbl_tag without change the above query (that the SELECT in SELECT part will be dynamic) to get a reference to tbl_tag
To get this:
+----+---------------+-----------+-----------+--------------+
| id | date | title | alt | new_column |
+----+---------------+-----------+-----------+--------------+
| 1 | 2018-10-10 | test1-1 | test1-3 | NULL |
| 2 | 2018-10-11 | test2-1 | test2-1 | NULL |
+----+---------------+-----------+-----------+--------------+
It would be great to get an idea or help.
Thanks
Your last comment on your question about using JOIN makes it clearer to me (I think) what you are after. JOINs will definitely help you a lot here, in place of the rather cumbersome query you are currently using.
Try this:
SELECT
tbl_main.date,
tblA.value AS title,
tblB.value AS alt
FROM
tbl_main
INNER JOIN (SELECT main_id, tag_id, value
FROM tbl_scd
INNER JOIN tbl_tag ON (tbl_scd.tag_id = tbl_tag.id)
WHERE tbl_tag.name = 'title') tblA
ON (tbl_main.id = tblA.main_id)
INNER JOIN (SELECT main_id, tag_id, value
FROM tbl_scd
INNER JOIN tbl_tag ON (tbl_scd.tag_id = tbl_tag.id)
WHERE tbl_tag.name = 'alt') tblB
ON (tbl_main.id = tblB.main_id);
I think this will get you much closer to a general solution to what it looks like you are trying to achieve, or at least point you in a good direction with using JOINs.
I also think you might benefit from re-thinking your database design, because this kind of pivoting rows from one table into columns in a query output can be an indicator that the data might be better off structured differently.
In any case, I hope this helps.
Supposing I have a table where a material has asignments of different characteristics. A material can have one or more charateristics. Then I would like to find to a certain material similar materials, that means at least 2 characteristics should match. In this example I should find material C when I compare with A and D should find B. Is there any solution in SQL?
material | character
----------------------
A | 2
A | 5
B | 1
B | 3
B | 4
C | 2
C | 5
D | 3
D | 1
This is an Entity-Attribute-Value table, and it notoriously painful to search. (In this case, the value is implied as being TRUE for has this attribute.)
It involves comparing everything against everything, grouping the results, and checking if the groups match. Virtually no use of indexes or intelligence of any kind.
SELECT
material_a.material AS material_a,
material_b.material AS material_b
FROM
material AS material_a
LEFT JOIN
material AS material_b
ON material_a.character = material_b.character
AND material_a.material <> material_b.material
GROUP BY
material_a.material,
material_b.material
HAVING
0 = MAX(CASE WHEN material_b.character IS NULL THEN 1 ELSE 0 END)
This gives every material_b that has all of the characteristics that material_a has.
- The HAVING clause will check that every 0 of material a's characteristics are missing from material b.
Changing to an INNER JOIN and changing the HAVING CLAUSE will get the share at least two materials.
SELECT
material_a.material AS material_a,
material_b.material AS material_b
FROM
material AS material_a
INNER JOIN
material AS material_b
ON material_a.character = material_b.character
AND material_a.material <> material_b.material
GROUP BY
material_a.material,
material_b.material
HAVING
COUNT(*) >= 2
Either way, you still are joining the whole table against the whole table, then filtering out the failures. With 100 materials, that's 9,900 material-material comparison. Imagine when you have 1000 materials and have 999,000 comparisons. Or 1million materials...
You could use something like the following grouped table to determine all items with more than 2 similar characteristics
SELECT
material = t1.material
, similarMaterial = t2.material
FROM
tableName t1
INNER JOIN tableName t2 ON t1.character = t2.character AND NOT(t1.material = t2.material)
GROUP BY material
HAVING
COUNT(*) >= 2
Yes, you can find all paired of similar materials with SQL similar to this:
SELECT c1.material, c2.material, COUNT(*) as characterCount
FROM charateristics c1
CROSS JOIN charateristics c2
WHERE c1.material > c2.material AND c1.character = c2.character
GROUP BY c1.material, c2.material
HAVING characterCount >= 2;
This would give you the results based on a material input:
SELECT b.material
FROM table1 a
INNER JOIN table1 b
ON a.character = b.character AND a.material <> b.material
WHERE a.material = 'A' -- Your input
GROUP BY b.material
HAVING COUNT(*) > 1;
sqlfiddle demo
Or do this to give you the pairs:
SELECT a.material as LEFT_MATERIAL ,b.material AS RIGHT_MATERIAL
FROM table1 a
INNER JOIN table1 b ON a.character = b.character AND a.material <> b.material
GROUP BY a.material,b.material
HAVING COUNT(*) > 1;
sqlfiddle demo
This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
How can I modify this query with two Inner Joins so that it stops giving duplicate results?
I'm having trouble getting my query to work.
SELECT itpitems.identifier, itpitems.name, itpitems.subtitle, itpitems.description, itpitems.itemimg, itpitems.mainprice, itpitems.upc, itpitems.isbn, itpitems.weight, itpitems.pages, itpitems.publisher, itpitems.medium_abbr, itpitems.medium_desc, itpitems.series_abbr, itpitems.series_desc, itpitems.voicing_desc, itpitems.pianolevel_desc, itpitems.bandgrade_desc, itpitems.category_code, itprank.overall_ranking, itpitnam.name AS artist, itpitnam.type_code FROM itpitems
INNER JOIN itprank ON (itprank.item_number = itpitems.identifier)
INNER JOIN (SELECT DISTINCT type_code FROM itpitnam) itpitnam ON (itprank.item_number = itpitnam.item_number)
WHERE mainprice > 1
LIMIT 3
I keep getting Unknown column 'itpitnam.name' in 'field list'.
However, if I change DISTINCT type_code to *, I do not get that error, but I do not get the results I want either.
This is a big result table so I am making a dummy example...
With *, I get something like:
+-----------+---------+----------+
| identifier| name | type_code|
+-----------+---------+----------+
| 2 | Joe | A |
| 2 | Amy | R |
| 7 | Mike | B |
+-----------+------------+-------+
The problem here is that I have two instances of identifier = 2 because the type_code is different. I have tried GROUP BY at the outside end of the query, but it is sifting through so many records it creates too much strain on the server, so I'm trying to find an alternative way of getting the results I need.
What I want to achieve (using the same dummy output) would look something like this:
+-----------+---------+----------+
| identifier| name | type_code|
+-----------+---------+----------+
| 2 | Joe | A |
| 7 | Mike | B |
| 8 | Sam | R |
+-----------+------------+-------+
It should skip over the duplicate identifier regardless if type_code is different.
Can someone help me modify this query to get the results as simulated in the above chart?
One approach is to use an inline view, like the query you already have. But instead of using DISTINCT, you would use a GROUP BY to eliminate duplicates. The simplest inline view to satisfy your requirements would be:
( SELECT n.item_number, n.name, n.type_code
FROM itpitnam n
GROUP BY n.item_number
) itpitnam
Although its not deterministic as to which row from itpitnam the values for name and type_code are retrieved from. A more elaborate inline view can make this more specific.
Another common approach to this type of problem is to use a correlated subquery in the SELECT list. For returning a small set of rows, this can perform reasonably well. But for returning large sets, there are more efficient approaches.
SELECT i.identifier
, i.name
, i.subtitle
, i.description
, i.itemimg
, i.mainprice
, i.upc
, i.isbn
, i.weight
, i.pages
, i.publisher
, i.medium_abbr
, i.medium_desc
, i.series_abbr
, i.series_desc
, i.voicing_desc
, i.pianolevel_desc
, i.bandgrade_desc
, i.category_code
, r.overall_ranking
, ( SELECT n1.name
FROM itpitnam n1
WHERE n1.item_number = r.item_number
ORDER BY n1.type_code, n1.name
LIMIT 1
) AS artist
, ( SELECT n2.type_code
FROM itpitnam n2
WHERE n2.item_number = r.item_number
ORDER BY n2.type_code, n2.name
LIMIT 1
) AS type_code
FROM itpitems i
JOIN itprank r
ON r.item_number = i.identifier
WHERE mainprice > 1
LIMIT 3
That query will return the specified resultset, with one significant difference. The original query shows an INNER JOIN to the itpitnam table. That means that a row will be returned ONLY of there is a matching row in the itpitnam table. The query above, however, emulates an OUTER JOIN, the query will return a row when there is no matching row found in itpitnam.
UPDATE
For best performance of those correlated subqueries, you'll want an appropriate index available,
... ON itpitnam (item_number, type_code, name)
That index is most appropriate because it's a "covering index", the query can be satisfied entirely from the index without referencing data pages in the underlying table, and there's equality predicate on the leading column, and an ORDER BY on the next two columns, so that will a avoid a "sort" operation.
--
If you have a guarantee that either the type_code or name column in the itpitnam table is NOT NULL, you can add a predicate to eliminate the rows that are "missing" a matching row, e.g.
HAVING artist IS NOT NULL
(Adding that will likely have an impact on performance.) Absent that kind of guarantee, you'd need to add an INNER JOIN or a predicate that tests for the existence of a matching row, to get an INNER JOIN behavior.
SELECT a.*
b.overall_ranking,
c.name AS artist,
c.type_code
FROM itpitems a
INNER JOIN itprank b
ON b.item_number = a.identifier
INNER JOIN itpitnam c
ON b.item_number = c.item_number
INNER JOIN
(
SELECT item_number, MAX(type_code) code
FROM itpitnam
GROUP BY item_number
) d ON c.item_number = d.item_number AND
c.type_code = d.code
WHERE mainprice > 1
LIMIT 3
Follow-up question: can you please post the table schema and how are the tables related with each other? So I will know what are the columns to be linked.