media
id | title | ...
1 | a song |
2 | a video |
media setting
media_id | setting_id | chosen_option
1 | 1 | 2
1 | 2 | 3
2 | 1 | 1
2 | 2 | 4
So I have media table with various infromation about user uploaded media files and they have two settings 1.privacy( option-1 for public and option-2 for private) and 2.age-safty( option-3 is for all and option-4 is for adult only). Now when a user(adult) searching for a media, suppose with a title starts with a.....
Here is my query:
SELECT
m.id AS media_id, m.title AS media_title,
ms.setting_id AS setting, ms.chosen_option as opt
FROM media m
LEFT JOIN media_setting ms ON m.id = ms.media_id
WHERE m.title LIKE 'a%'
AND It will give me an output with duplicate rows one row with each setting which I don't want.
So what i want is :
media_id | media_title | setting_1 | option_for_1 | setting_2 | option_for_2
1 | a song | 1 | 2 | 2 | 3
2 | a video | 1 | 1 | 2 | 4
How can i achieve this? Thanks.
As per comments, I'd stick with the query you've got, and resolve the display issues in application code.
But anyway, here's a standard (and non-dynamic) approach in sql...
CREATE TABLE media
(id SERIAL PRIMARY KEY
,title VARCHAR(20) NOT NULL
);
INSERT INTO media VALUES
(1,'a song'),
(2,'a video');
DROP TABLE IF EXISTS media_setting;
CREATE TABLE media_setting
(media_id INT NOT NULL
,setting_id INT NOT NULL
,chosen_option INT NOT NULL
,PRIMARY KEY(media_id,setting_id)
);
INSERT INTO media_setting VALUES
(1,1,2),
(1,2,3),
(2,1,1),
(2,2,4);
SELECT m.*
, MAX(CASE WHEN s.setting_id = 1 THEN chosen_option END) option_for_1
, MAX(CASE WHEN s.setting_id = 2 THEN chosen_option END) option_for_2
FROM media m
LEFT
JOIN media_setting s
ON s.media_id = m.id
GROUP
BY m.id;
+----+---------+--------------+--------------+
| id | title | option_for_1 | option_for_2 |
+----+---------+--------------+--------------+
| 1 | a song | 2 | 3 |
| 2 | a video | 1 | 4 |
+----+---------+--------------+--------------+
Related
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.
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);
I'm developing a system to manage rental processes right now and I'm wondering how to efficiently query all rentable objects with the person name, who is currently renting it, if the object is rented at the moment. Otherwise there should be NULL in that column.
My tables look like:
object
| object_id | object_name |
---------------------------
| 1 | Object A |
| 2 | Object B |
| 3 | Object C |
| 4 | Object D |
| 5 | Object E |
---------------------------
person
| person_id | person_name |
---------------------------
| 1 | John Doe |
| 2 | Jane Doe |
| 3 | Max Muster |
| 4 | Foobar |
---------------------------
rental
| rental_id | rental_state| person_person_id |
----------------------------------------------
| 1 | open | 1 |
| 2 | returned | 1 |
| 3 | returned | 2 |
| 4 | open | 3 |
| 5 | returned | 4 |
----------------------------------------------
rental2object
| rental_rental_id | object_object_id |
---------------------------------------
| 1 | 1 |
| 2 | 2 |
| 2 | 3 |
| 3 | 3 |
| 4 | 2 |
| 4 | 5 |
| 5 | 2 |
---------------------------------------
The result I want should look like this:
| object_id | object_name | rented_to |
-------------------------------------------
| 1 | Object A | John Doe |
| 2 | Object B | Max Muster |
| 3 | Object C | NULL |
| 4 | Object D | NULL |
| 5 | Object E | Max Muster |
-------------------------------------------
What I've got so far is:
SELECT `object_id`, `object_name`, `person_name` FROM `object`
LEFT JOIN `rental2object` ON `object_id` = `object_object_id`
LEFT JOIN `rental` ON `rental_id` = `rental_rental_id` AND `rental_state` = 'open'
LEFT JOIN `person` ON `person_id` = `person_person_id`
GROUP BY `object_id`
The obvious problem is that I don't know how to aggregate the right way while grouping.
What would be the most efficient way to achieve my goal? Appreciate your help.
EDIT
Corrected the expected result, so that Object B is also rented to Max Muster.
About your question
Objects #2 and #5 are both in rental #4. But, on your expected results, you are handling both in different way. Object E and Object B both should be the same behaviour because they are in the same rental. If not, you should to explain witch is the criteria to know if a product has or not a related person.
Group by
To be SQL92 compliant you should to include in select clause all nonaggregated columns:
SELECT `object_id`, `object_name`, `person_name` as rented_to
FROM `object`
...
GROUP BY `object_id`, `object_name`, `person_name`
To be SQL99 compliant you should to include in select clause all nonaggregated columns non functionlly dependent, in your case, they are a dependent between object_id and object_name: object_id -> object_name (the field rental_state breaks dependent functionality to person), then you can just to write:
SELECT `object_id`, `object_name`, `person_name` as rented_to
FROM `object`
...
GROUP BY `object_id`, `person_name`
MySQL 5.7.5 and up implements detection of functional dependence, then this last select is valid but I suggest to you that, for readability, use the first one.
Read MySQL Handling of GROUP BY for more info and ONLY_FULL_GROUP_BY parameter details.
Performance
Be sure you have indexes for:
object: Object_id ( is primary key, then index is implicit )
rental2object: object_object_id ( may be a composite index with the other field, but be sure object_object_id is the first field on index )
rental : rental_id & rental_state ( a composite index with both fields )
person: person_id ( is primary key, then index is implicit )
Try this
SELECT
o.object_id,
o.object_name,
p.person_name AS rent_to
FROM
rental2object ro
RIGHT JOIN object o ON ro.object_object_id = o.object_id
LEFT JOIN rental r ON ro.rental_rental_id = r.rental_id AND r.rental_status = 'open'
JOIN person p ON r.person_person_id = p.person_id
SELECT `object_id`, `object_name`,
case
when rental_state = 'Open' then `person_name`
when r1.rental_rental_id is null then null
else `rental_state`
end as RentedTo
FROM `object`
LEFT JOIN `rental2object` r1 ON `object_id` = r1.`object_object_id`
LEFT JOIN `rental` ON `rental_id` = r1.`rental_rental_id`
LEFT JOIN `person` ON `person_id` = `person_person_id`
where r1.rental_rental_id =
(select max(r2.`rental_rental_id`)
from `rental2object` r2
where r2.`object_object_id` = r1.`object_object_id`
group by r2.`object_object_id`)
or r1.rental_rental_id is null
GROUP BY `object_id`;
I've been trying to guess how to solve my problem for some time and I cannot seem to find a solution, so I come to you, experts.
What I've got
A MySQL table with the following structure and values (as an example):
+----+---------+----------------+-----------------+--------------+
| id | item_id | attribute_name | attribute_value | deleted_date |
+----+---------+----------------+-----------------+--------------+
| 1 | 2 | action | call | NULL |
| 2 | 2 | person | Joseph | NULL |
| 3 | 2 | action | fault | NULL |
| 4 | 2 | otherattr | otherval | NULL |
| 5 | 5 | action | call | NULL |
| 6 | 5 | person | Mike | NULL |
| 7 | 5 | action | sprint | NULL |
| 8 | 8 | action | call | NULL |
| 9 | 8 | person | Joseph | NULL |
| 10 | 8 | action | block | NULL |
| 11 | 8 | action | call | NULL |
+----+---------+----------------+-----------------+--------------+
What I need
I'd like a query to return me how many items (item_id) have at least one attribute_name with 'action' and with attribute_value as 'call', grouped by 'person', but only counting one of them.
So, if - like in the example, at ids 8 and 11 - there is an item_id with two "action" = "call", only COUNT one of them.
The query should return something like this:
+--------+--------------+
| person | action_calls |
+--------+--------------+
| Joseph | 2 |
| Mike | 1 |
+--------+--------------+
The problem
The problem is that I don't know how to do that in a simple way that would not make a huge performance increment, as this query will be returning and searching along a lot of rows - and returning a lot of them, too, in some cases.
The only thing that comes to my mind is with nested and nested queries, and I'd like to avoid that.
If I make a COUNT(DISTINCT), it only returns '1' in 'Joseph', because the value is always 'call', and if I GROUP BY b.item_id, it returns me two rows with Joseph (and, in this case too, it counts both 'call' attributes, so it wouldn't be the solution neither).
What I've tried
The query that I've tried is the following:
SELECT a.attribute_value AS person, COUNT(b.attribute_value) AS action_calls
FROM `er_item_attributes` a, `er_item_attributes` b
WHERE a.attribute_name = 'person'
AND b.item_id IN (SELECT DISTINCT item_id FROM er_item_parents WHERE parent_id IN (1234,4567))
AND b.item_id = a.item_id
AND b.attribute_name = 'action'
AND b.attribute_value = 'call'
AND b.deleted_date IS NULL
GROUP BY a.attribute_value, b.attribute_name
Additional information
The item_id, as you can see, will be also chosen from an inner WHERE clause, because the ones that are valid are in another table (just like a parent - son table). The parent_id numbers are for an example and are not relevant.
To sum up
How can I make a COUNT in MySQL to behave like a COUNT GROUP BY without nesting SELECTs that could deteriorate the performance?
If any further information was needed, comment it and I will try to add it.
Also, any recommendations on another way to query the information needed to improve performance will be welcome.
Thank you everyone for your time and help!
Kind regards.
Try this!
SELECT attribute_value AS person, COUNT(*) FROM `stack_1239`
WHERE item_id IN (
SELECT item_id FROM `stack_1239` WHERE attribute_name = 'action' AND attribute_value = 'call'
)
AND attribute_name = 'person'
GROUP BY person;
:)
DROP TABLE IF EXISTS eav_hell;
CREATE TABLE eav_hell
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,entity INT NOT NULL
,attribute VARCHAR(20) NOT NULL
,value VARCHAR(20) NOT NULL
);
INSERT INTO eav_hell
VALUES
( 1 ,2 ,'action','call'),
( 2 ,2 ,'person','Joseph'),
( 3 ,2 ,'action','fault'),
( 4 ,2 ,'otherattr','otherval'),
( 5 ,5 ,'action','call'),
( 6 ,5 ,'person','Mike'),
( 7 ,5 ,'action','sprint'),
( 8 ,8 ,'action','call'),
( 9 ,8 ,'person','Joseph'),
(10 ,8 ,'action','block'),
(11 ,8 ,'action','call');
SELECT e1.entity
, e1.value person
, e2.value action
, COUNT(*)
FROM eav_hell e1
LEFT
JOIN eav_hell e2
ON e2.entity = e1.entity
AND e2.attribute = 'action'
AND e2.value = 'call'
WHERE e1.attribute = 'person'
GROUP
BY entity
, person
, action;
+--------+--------+--------+----------+
| entity | person | action | COUNT(*) |
+--------+--------+--------+----------+
| 2 | Joseph | call | 1 |
| 5 | Mike | call | 1 |
| 8 | Joseph | call | 2 |
+--------+--------+--------+----------+
Edit:
SELECT e1.value person
, e2.value action
, COUNT(DISTINCT e1.entity)
FROM eav_hell e1
LEFT
JOIN eav_hell e2
ON e2.entity = e1.entity
AND e2.attribute = 'action'
AND e2.value = 'call'
WHERE e1.attribute = 'person'
GROUP
BY person
, action;
+--------+--------+---------------------------+
| person | action | COUNT(DISTINCT e1.entity) |
+--------+--------+---------------------------+
| Joseph | call | 2 |
| Mike | call | 1 |
+--------+--------+---------------------------+
I have a debating competition where I want to find out which team has won and lost the most debates. The problem I am having is the id of the two teams competing in the debate is in two different columns (hostid, visitid).
I have the below so far which gives me what I want however it only shows the visitid data.
CREATE TABLE teams (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255)
) DEFAULT CHARACTER SET utf8 ENGINE=InnoDB;
CREATE TABLE debates (
debateid INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
debatedate DATE NOT NULL,
hostid INT,
visitid INT,
winnerid INT
) DEFAULT CHARACTER SET utf8 ENGINE=InnoDB;
INSERT INTO teams (id, name) VALUES
(1,'team one'),
(2,'team two'),
(3,'team three'),
(4,'team four'),
(5,'team five'),
(6,'team six');
INSERT INTO debates (debateid, debatedate,hostid, visitid, winnerid ) VALUES
(1,'2012-01-11', 1,2,1),
(2,'2012-01-11', 3,4,4),
(3,'2012-02-11', 5,6,5),
(4,'2012-02-11', 1,4,1),
(5,'2012-02-11', 2,5,5),
(6,'2012-02-11', 3,6,3),
(7,'2012-03-11', 6,1,1),
(8,'2012-03-11', 5,2,5),
(9,'2012-03-11', 3,4,4);
SELECT
visitid AS id,
t.name AS name,
sum(visitid= deb.winnerid) as w,
sum(visitid != deb.winnerid) as l
FROM debates AS deb
JOIN teams t ON t.id = deb.visitid
WHERE visitid != -1
AND debatedate < CURDATE( )
GROUP BY id
ORDER BY w DESC
RESULT
-----------------------------------------
| ID | NAME | W | L |
| 4 | team four | 2 | 1 |
| 5 | team five | 1 | 0 |
| 1 | team one | 1 | 0 |
| 6 | team six | 0 | 2 |
| 2 | team two | 0 | 2 |
-----------------------------------------
How can I combine these two columns, I am aware of union but I can't think of a way to implement this in this situation or what method I should use?
If I had it working as intended the result would be the below eg where hostid or visitid = winnerid
-----------------------------------------
| ID | NAME | W | L |
| 1 | team one | 3 | 0 |
| 5 | team five | 3 | 0 |
| 4 | team four | 2 | 1 |
| 3 | team three | 1 | 2 |
| 2 | team two | 0 | 3 |
| 6 | team six | 0 | 3 |
-----------------------------------------
See fiidle for example
SELECT t.id,
t.name,
SUM(t.id = d.winnerid) AS w,
SUM(t.id != d.winnerid) AS l
FROM debates AS d
JOIN teams AS t ON t.id IN (d.hostid, d.visitid)
WHERE d.visitid != -1 -- not sure what purpose this serves
AND d.debatedate < CURDATE()
GROUP BY t.id
ORDER BY w DESC
See it on sqlfiddle.