MySQL - How get this result? - mysql

I have a two tables.
work:
+----+----------+
| id | position |
+----+----------+
| 1 | 1 |
| 2 | 2 |
+----+----------+
content:
+----+---------+------+-------------+
| id | work_id | name | translation |
+----+---------+------+-------------+
| 1 | 1 | Kot | 1 |
| 2 | 1 | Cat | 2 |
| 3 | 2 | Ptak | 1 |
| 4 | 2 | Bird | 2 |
| 5 | 2 | Ssss | 3 |
+----+---------+------+-------------+
I want to get result like this:
+----+------+----------+
| id | name | sortName |
+----+------+----------+
| 1 | Kot | NULL |
| 1 | Cat | NULL |
| 2 | Ptak | Ssss |
| 2 | Bird | Ssss |
+----+------+----------+
My not working query is here:
select
w.id,
c.name,
cSort.name as sortName
from
work w
LEFT JOIN
content c
ON
(w.id=c.work_id)
LEFT JOIN
content cSort
ON
(w.id=cSort.work_id)
WHERE
c.translation IN(1,2) AND
cSort.translation=3
ORDER BY
sortName
I want to get for each work at least one translation and secound if exist (translation=1 always exist). And for every row I want special column with translation used to sort. But Not always this translation exist for work.id. In this example I want to sort work by translation=3.
Sorry for my not fluent english. Any ideas?
Best regards

/*
create table work ( id int, position int);
insert into work values
( 1 , 1 ),
( 2 , 2 );
create table content(id int, work_id int, name varchar(4), translation int);
insert into content values
( 1 , 1 , 'Kot' , 1),
( 2 , 1 , 'Cat' , 2),
( 3 , 2 , 'Ptak' , 1),
( 4 , 2 , 'Bird' , 2),
( 5 , 2 , 'Ssss' , 3);
*/
select w.id,c.name,(select c.name from content c where c.work_id = w.id and c.translation = 3) sortname
from work w
join content c on w.id = c.work_id
where c.translation <> 3;
result
+------+------+----------+
| id | name | sortname |
+------+------+----------+
| 1 | Kot | NULL |
| 1 | Cat | NULL |
| 2 | Ptak | Ssss |
| 2 | Bird | Ssss |
+------+------+----------+

So translation is also a work_id and you consider translation = 3 a translation in your example and translation <> 3 an original. You want to join each original record with every translation record where the latter's work_id matches the former's translation.
I think you are simply confusing IDs here. It should be ON (w.translation = cSort.work_id).
Another way to write the query:
select o.work_id as id, o.name, t.name as sortname
from (select * from content where translation <> 3) o
left join (select * from content where translation = 3) t
on t.work_id = o.translation
order by t.name;
There seems to be no need to join table work.
I'd like to add that the table design is a bit confusing. Somehow it is not clear from it what is a translation for what. In your example you interpret translation 3 as a translation for the non-three records, but this is just an example as you say. I don't find this readable.
UPDATE: In order to sort your results by work.position, you can join that table or use a subquery instead. Here is the order by clause for the latter:
order by (select position from work w where w.id = o.work_id);

Related

Retrieving missing data from within the same table

I'm importing CSV files with missing data into a MariDB table. I need to find all codes that don't have a corresponding place = 2.
Table cityX
| id | code | place | value | description | subcode |
| 1 | 001x | 1 | 6.00 | unique str | A |
| 2 | 002x | 1 | 2.23 | diff string | B |
| 3 | 003x | 1 | 2.23 | another str | B |
Every code in the table must have a duplicate row with place = 1 and place = 2
| id | code | place | value | description | subcode |
| 1 | 001x | 1 | 6.00 | unique str | A |
| 2 | 001x | 2 | 6.00 | unique str | A |
I've used variations of select ... except statements to isolate the codes with varying amount of erroneous fields.
Using SELECT [code] FROM cityX WHERE place = '1' EXCEPT SELECT [code] FROM cityX where place = '2', creating a temporary table and joining the remaining place, value, description, and subcode fields to retrieve missing codes. I'm retrieving most of the missing records, but am introducing duplicates as well.
How can I properly select and insert rows missing a place = 2
This solution avoids EXCEPT which doesn't work in every RDBMS (not sure about mysql)
SELECT CODES.code,
CODE_W_1.place AS place_1,
CODE_W_2.place AS place_2
FROM (SELECT code
FROM cityx
GROUP BY code) AS CODES
LEFT OUTER JOIN (SELECT code,
place
FROM cityx
WHERE place = 1
GROUP BY code) AS CODE_W_1
ON CODES.code = CODE_W_1.code
LEFT OUTER JOIN (SELECT code,
place
FROM cityx
WHERE place = 2
GROUP BY code) AS CODE_W_2
ON CODES.code = CODE_W_2.code
WHERE code_w_1 IS NULL
OR CODE_W_2.code IS NULL
I don't have access to mysql to test this, but I got this from Rasgo which automatically writes SQL.
We can use selects which test whether the other values exists. We can either use the queries alone to check for unmatched values, or in an insert to add the missing values.
create table cityX (
id int primary key not null auto_increment,
code char(5),
place int );
insert into cityX (code, place) values
('001x',1),('001x',2),('002x',1),('003x',2);
select * from cityX
order by code, place;
id | code | place
-: | :--- | ----:
1 | 001x | 1
2 | 001x | 2
3 | 002x | 1
4 | 003x | 2
insert into cityX (code, place)
select x.code,1 from cityX x
where place = 2
and not exists
(select id from cityX c
where c.code = x.code
and place = 1);
insert into cityX (code, place)
select x.code,2 from cityX x
where place = 1
and not exists
(select id from cityX c
where c.code = x.code
and place = 2);
select * from cityX
order by code, place;
id | code | place
-: | :--- | ----:
1 | 001x | 1
2 | 001x | 2
3 | 002x | 1
6 | 002x | 2
5 | 003x | 1
4 | 003x | 2
db<>fiddle here

MySQL - join multiple mapped tables and count records with different mapping conditions

It's the 3rd day I'm trying to write a MySQL query. Did lots of search, but it still doesn't work as expected. I'll try to simplify tables as much as possible
System has tkr_restaurants table:
restaurant_id | restaurant_name
1 | AA
2 | BB
3 | CC
Each restaurant has a division assigned (tkr_divisions table):
division_id | restaurant_id | division_name
1 | 1 | AA-1
2 | 1 | AA-2
3 | 2 | BB-1
Then there are meals in tkr_meals_to_restaurants_divisions table, where each meal can be assigned (mapped) to whole restaurant(s) and/or specific division(s). If meal is mapped to restaurant, all restaurant's divisions should see it. If meal is mapped to division(s), only specific division(s) should see it.
meal_id | mapped_restaurant_id | mapped_division_id
1 | 1 | NULL
2 | NULL | 1
3 | NULL | 2
I need to display a list of restaurants and number of meals mapped to it depending on user permissions.
Example 1: if user has permissions to access whole restaurant_id 1 and restaurant_3 (and no specific divisions), then list should be:
AA | 3
CC | 0
(because user can access meals mapped to restaurant 1 + all its division, and restaurant 3 + all its divisions (even if restaurant 3 has no divisions/meals mapped))
Example 2: if user has permissions to access only division_id 1, then list should be:
AA | 1
(because user can only access meals mapped to division 1).
The closest query I could get is:
Example 1:
SELECT *,
(SELECT COUNT(DISTINCT meal_id)
FROM
tkr_meals_to_restaurants_divisions
WHERE
tkr_meals_to_restaurants_divisions.mapped_restaurant_id=tkr_restaurants.restaurant_id
OR tkr_meals_to_restaurants_divisions.mapped_division_id=tkr_divisions.division_id)AS total_meals
FROM
tkr_restaurants
LEFT JOIN
tkr_divisions
ON tkr_restaurants.restaurant_id=tkr_divisions.restaurant_id
WHERE
tkr_restaurants.restaurant_id IN (1, 3)
OR tkr_restaurants.restaurant_id IN (
SELECT restaurant_id
FROM tkr_divisions
WHERE division_id IN (NULL)
)
GROUP BY
tkr_restaurants.restaurant_id
ORDER BY
tkr_restaurants.restaurant_name
However, result was:
AA | 2
CC | 0
I believe I'm greatly over-complicating this query, but all the simpler queries I wrote produced even more inaccurate results.
What about this query:
SELECT
FROM tkr_restaurants AS a
JOIN tkr_divisions AS b
ON a.restaurant_id = b.restaurant_id
LEFT OUTER JOIN tkr_meals_to_restaurants_divisions AS c
ON (c.mapped_restaurant_id = a.restaurant_id OR c.mapped_division_id = b.division_id)
As a Base four your further work. It combine all information into one table. If you add e.g. this:
WHERE a.restaurant_id IN (1, 3)
the result will be
| restaurant_id | restaurant_name | division_id | restaurant_id | division_name | meal_id | mapped_restaurant_id | mapped_division_id |
|---------------|-----------------|-------------|---------------|---------------|---------|----------------------|--------------------|
| 1 | AA | 1 | 1 | AA-1 | 1 | 1 | (null) |
| 1 | AA | 2 | 1 | AA-2 | 1 | 1 | (null) |
| 1 | AA | 1 | 1 | AA-1 | 2 | (null) | 1 |
| 1 | AA | 2 | 1 | AA-2 | 3 | (null) | 2 |
just count the distinct meal ids with COUNT(DISTINCT c.meal_id) and take the restaurant name to get AA: 3 for your example 2
I used a sqlfiddle: http://sqlfiddle.com/#!9/fa2b78/18/0
[EDIT]
Change JOIN tkr_divisions AS b to LEFT OUTER JOIN tkr_divisions AS b
Change SELECT * to SELECT a.restaurant_name, COUNT(DISTINCT c.meal_id)
Add a GROUP BY a.restaurant_name at the end.
Update the SQL Fiddle (new link)

Getting no of products in all categories and parent categories contains a keyword

I am trying to fetch all the categories and their count (no of products in that category) of those products where keyword matches. The query I tried doesn't give me the correct result.
Also I want the parent categories till level 1 and their count as well.
e.g. I am trying with keyword watch, then category "watches" should be there with some count. Also the parent category "accessories" with the sum of its descendant categories count.
my table structures are:
tblProducts: There are 5 categories of a product, fldCategoryId1, fldCategoryId2, fldCategoryId3, fldCategoryId4 and fldCategoryId5. fldProductStatus should be 'A'
+-----------------------------+-------------------+
| Field | Type |
+-----------------------------+-------------------+
| fldUniqueId | bigint(20) |
| fldCategoryId1 | bigint(20) |
| fldCategoryId2 | bigint(20) |
| fldCategoryId3 | bigint(20) |
| fldCategoryId4 | bigint(20) |
| fldCategoryId5 | bigint(20) |
| fldProductStatus | enum('A','P','D') |
| fldForSearch | longtext |
+-----------------------------+-------------------+
tblCategory:
+------------------------------+-----------------------+
| Field | Type |
+------------------------------+-----------------------+
| fldCategoryId | bigint(20) |
| fldCategoryName | varchar(128) |
| fldCategoryParent | int(11) |
| fldCategoryLevel | enum('0','1','2','3') |
| fldCategoryActive | enum('Y','N') |
+------------------------------+-----------------------+
Search Query:
SELECT count( c.fldCategoryId ) AS cnt, c.fldCategoryLevel, c.fldCategoryParent, c.fldCategoryId, c.fldCategoryName, p.fldForSearch, c.fldCategoryParent
FROM tblCategory c, tblProducts p
WHERE (
c.fldCategoryId = p.fldCategoryId1
OR c.fldCategoryId = p.fldCategoryId2
OR c.fldCategoryId = p.fldCategoryId3
OR c.fldCategoryId = p.fldCategoryId4
OR c.fldCategoryId = p.fldCategoryId5
)
AND p.fldProductStatus = 'A'
AND (
MATCH ( p.fldForSearch )
AGAINST (
'+(watches watch)'
IN BOOLEAN MODE
)
)
GROUP BY c.fldCategoryId
Note: The table is in the InnoDB engine and have FULLTEXT search index on 'fldForSearch' column.
EDIT: sample data can be found in sqlfiddle
I'm not sure what you mean by:
Also I want the parent categories till level 1 and their count as well.
But the following query will show you a count for each category (including those with 0 found products), and a general rollup:
SELECT
c.fldCategoryId,
c.fldCategoryLevel,
c.fldCategoryName,
COUNT( * ) AS cnt
FROM tblCategory c
LEFT JOIN tblProducts p ON
(c.fldCategoryId = p.fldCategoryId1
OR c.fldCategoryId = p.fldCategoryId2
OR c.fldCategoryId = p.fldCategoryId3
OR c.fldCategoryId = p.fldCategoryId4
OR c.fldCategoryId = p.fldCategoryId5)
AND p.fldProductStatus = 'A'
AND MATCH ( p.fldForSearch )
AGAINST (
'+(watches watch)'
IN BOOLEAN MODE
)
GROUP BY
c.fldCategoryId
c.fldCategoryLevel,
c.fldCategoryName
WITH ROLLUP;
Notes:
you cannot select p.fldForSearch if you expect a count of all the products in the category. fldForSearch is on a per product basis, it defeats the grouping purpose
I left joined with products so it returns the categories with 0 products matching your keywords. If you don't want this to happen just remove the LEFT keyword
I haven't checked the MATCH condition I assume it's correct.
Start by not splaying an array (fldCategoryId...) across columns. Instead, add a new table.
Once you have done that, the queries change, such as getting rid of OR clauses.
Hopefully, any further issues will fall into place.
Since your category tree has a fixed height (4 levels), you can create a transitive closure table on the fly with
SELECT c1.fldCategoryId AS descendantId, c.fldCategoryId AS ancestorId
FROM tblcategory c1
LEFT JOIN tblcategory c2 ON c2.fldCategoryId = c1.fldCategoryParent
LEFT JOIN tblcategory c3 ON c3.fldCategoryId = c2.fldCategoryParent
JOIN tblcategory c ON c.fldCategoryId IN (
c1.fldCategoryId,
c1.fldCategoryParent,
c2.fldCategoryParent,
c3.fldCategoryParent
)
The result will look like
| descendantId | ancestorId |
|--------------|------------|
| 1 | 1 |
| 2 | 1 |
| 2 | 2 |
| ... | ... |
| 5 | 1 |
| 5 | 2 |
| 5 | 5 |
| ... | ... |
You can now use it in a subquery (derived table) to join it with products using descendantId and with categories using ancestorId. That means that a product from category X will be indirectly associated with all ancestors of X (as well as with X). For example: Category 5 is a child of 2 - and 2 is a child of 1. So all products from category 5 must be counted for categories 5, 2 and 1.
Final query:
SELECT c.*, coalesce(sub.cnt, 0) as cnt
FROM tblCategory c
LEFT JOIN (
SELECT tc.ancestorId, COUNT(DISTINCT p.fldUniqueId) AS cnt
FROM tblProducts p
JOIN (
SELECT c1.fldCategoryId AS descendantId, c.fldCategoryId AS ancestorId
FROM tblcategory c1
LEFT JOIN tblcategory c2 ON c2.fldCategoryId = c1.fldCategoryParent
LEFT JOIN tblcategory c3 ON c3.fldCategoryId = c2.fldCategoryParent
JOIN tblcategory c ON c.fldCategoryId IN (
c1.fldCategoryId,
c1.fldCategoryParent,
c2.fldCategoryParent,
c3.fldCategoryParent
)
) tc ON tc.descendantId IN (
p.fldCategoryId1,
p.fldCategoryId2,
p.fldCategoryId3,
p.fldCategoryId4,
p.fldCategoryId5
)
WHERE p.fldProductStatus = 'A'
AND MATCH ( p.fldForSearch )
AGAINST ( '+(watches watch)' IN BOOLEAN MODE )
GROUP BY tc.ancestorId
) sub ON c.fldCategoryId = sub.ancestorId
Result for your sample data (without level, since it seems to be wrong anyway):
| fldCategoryId | fldCategoryName | fldCategoryParent | fldCategoryActive | cnt |
|---------------|-----------------|-------------------|-------------------|-----|
| 1 | Men | 0 | Y | 5 |
| 2 | Accessories | 1 | Y | 5 |
| 3 | Men Watch | 1 | Y | 3 |
| 5 | Watch | 2 | Y | 5 |
| 6 | Clock | 2 | Y | 3 |
| 7 | Wrist watch | 1 | Y | 2 |
| 8 | Watch | 2 | Y | 4 |
| 9 | watch2 | 3 | Y | 2 |
| 10 | fastrack | 8 | Y | 3 |
| 11 | swish | 8 | Y | 2 |
| 12 | digital | 5 | Y | 2 |
| 13 | analog | 5 | Y | 2 |
| 14 | dual | 5 | Y | 1 |
Demos:
sqlfiddle
rextester
Note that the outer (left joined) subquery is logically not necessary. But from my experience MySQL doesn't perform well without it.
There are still ways for performance optimisation. One is to store the transitive closure table in an indexed temporary table. You can also persist it in a regular table, if categories do rarely change. You can also manage it with triggers.

MySQL concat columns and rows from multiple tables

I'm trying to concatenate data from three related tables according to:
orders orderrow orderrow_op
+----+ +----+----------+ +----+-------------+
| id | | id | id_order | | id | id_orderrow |
+----+ +----+----------+ +----+-------------+
| 1 | | 1 | 1 | | 1 | 1 |
| 2 | | 2 | 1 | | 2 | 1 |
| 3 | | 3 | 2 | | 3 | 2 |
+----+ | 4 | 3 | | 4 | 3 |
+----+----------+ | 5 | 3 |
| 6 | 3 |
+----+-------------+
The result i'm looking for is something like:
orderops (Desired Result)
+----------+-----------------+
| id_order | id_row:id_ops |
+----------+-----------------+
| 1 | 1:(1,2); 2:(3); |
| 2 | 3:(4,5,6) |
| 3 | 4:NULL |
+----------+-----------------+
I.e i want the operations and rows all be displayed on one row related to the order. So far i've tried things like:
SELECT
db.orders.id AS orderid,
db.orderrow.id AS rowids,
GROUP_CONCAT(DISTINCT db.orderrow.id) AS a,
GROUP_CONCAT(db.orderrow.id, ':', db.orderrow_op.id) AS b
FROM
db.orders
LEFT JOIN db.orderrow ON db.orders.id = db.orderrow.id_order
LEFT JOIN db.orderrow_op ON db.orderrow.id = db.orderrow_op.id_orderrow
GROUP BY orderid
Where in column 'a' i get the row ids and in column 'b' i get the operation_ids with corresponding row_id prepended. I'd like to combine the two into a single column such that related values in 'b' will start of with id from 'a' and only show once.
I'm fairly new to MySQL so i don't know if this is even possible or if i'ts a good idea at all? The aim is to structure the data into JSON for delivery via REST application so perhaps it's better to deliver the rows directly to the webserver and handle json parsing over there? I just figured that this approach might be faster.
This is not the nicest query but it's working for your example table setup.
SELECT
o.id AS id_order,
group_concat(sub.ops
SEPARATOR ' ') AS id_row_id_ops
FROM
(SELECT
orderrow.id_order,
IF(isnull(l3.ops), concat(orderrow.id, ':', 'NULL'), concat(orderrow.id, ':', l3.ops)) as ops
FROM
orderrow
LEFT JOIN (SELECT
orderrow_op.id_orderrow,
concat('(', group_concat(orderrow_op.id), '); ') as ops
FROM
orderrow_op
GROUP BY orderrow_op.id_orderrow) l3 ON l3.id_orderrow = orderrow.id) sub
LEFT JOIN
orders o ON o.id = sub.id_order
GROUP BY o.id;
One of the things to mind is the LEFT JOIN and that you need to cast a "null" value to a "null" text (otherwise your element 4 will vanish).
The output:

conditional mysql query multiple tables

I have 2 tables in mysql database as shown below. I am looking for a query that will select * from books but if preview_image = 'none' then preview_image = the hash_id of the row with the largest size where books.id = images.parentid. Hope this makes sense.
table books
+----------------+---------------+
| id | title | preview_image |
+----------------+---------------+
| 1 | book1 | 55859076d906 |
| 2 | book2 | 20a14f9fd7cf |
| 3 | book3 | none |
| 4 | book4 | ce805ecff5c9 |
| 5 | book5 | e60a7217b3e2 |
+----------------+---------------+
table images
+-------------+------+---------------+
| parentid | size | hash_id |
+--------------------+---------------+
| 2 | 100 | 55859076d906 |
| 1 | 200 | 20a14f9fd7cf |
| 3 | 300 | 34805fr5c9e5 |
| 3 | 400 | ce805ecff5c9 |
| 3 | 500 | e60a7217b3e2 |
+--------------------+---------------+
Thanks
You can use SUBSTRING_INDEX() to obtain the first record from a sorted GROUP_CONCAT(), and switch using a CASE expression:
SELECT books.id, books.title, CASE books.preview_image
WHEN 'none' THEN SUBSTRING_INDEX(
GROUP_CONCAT(images.hash_id ORDER BY images.size DESC SEPARATOR ',')
, ',', 1)
ELSE books.preview_image
END AS preview_image
FROM books LEFT JOIN images ON images.parentid = books.id
GROUP BY books.id
Write a subquery that finds the desired hash ID for each parent ID, using one of the techniques in SQL Select only rows with Max Value on a Column. Then join this with the books table.
SELECT b.id, b.title, IF(b.preview_image = 'none', i.hash_id, b.preview_image) AS image
FROM books AS b
LEFT JOIN (SELECT i1.parentid, i1.hash_id
FROM images AS i1
JOIN (SELECT parentid, MAX(size) AS maxsize
FROM images
GROUP BY parentid) AS i2
ON i1.parentid = i2.parentid AND i1.size = i2.size) AS i
ON b.id = i.parentid