I have a posts/comments database that I am unable to order correctly.
I need it to be ordered primarily by its id but if its parent_id does not equal its id, it is placed after its parent and also these children would ordered by id.
Here is my current database.
CREATE TABLE `questions` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`parent_id` int(10) NOT NULL,
`entry_type` varchar(8) NOT NULL,
`entry_content` varchar(1024) NOT NULL,
`entry_poster_id` varchar(10) NOT NULL,
`entry_status` varchar(1) NOT NULL,
`entry_score` varchar(10) NOT NULL,
`time_posted` varchar(10) NOT NULL,
PRIMARY KEY (`id`),
KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=7 ;
--
-- Dumping data for table `questions`
--
INSERT INTO `questions` VALUES(1, 1, 'question', 'How do I does SQL?', 'CodyC', '0', '2', '1308641965');
INSERT INTO `questions` VALUES(2, 1, 'answer', 'Easy, you eat cheese!', 'PatrickS', '0', '-4', '1308641965');
INSERT INTO `questions` VALUES(3, 2, 'comment', 'WTF are you on noobass?!', 'FraserK', '0', '100', '1308641965');
INSERT INTO `questions` VALUES(4, 1, 'answer', 'blah', '5', '0', '0', '1308642204');
INSERT INTO `questions` VALUES(5, 4, 'comment', 'blah2', '4', '0', '0', '1308642247');
INSERT INTO `questions` VALUES(6, 2, '2', '3', '3', '3', '3', '3');
and my current query
SELECT *
FROM questions
WHERE parent_id =1
OR parent_id
IN (
SELECT id
FROM questions
WHERE parent_id =1
AND parent_id != id
)
how do I order so that order id to that each object comes after its parent, where the id = parent_id means is a base level and has no parent?
This seems to work:
SELECT *
FROM questions
order by case when parent_id != id then parent_id else id end, id;
But it depends whether you want grandchildren before children etc. Your question doesn't specify.
However, if you use this technique you can make your ordering term(s) as complicated as you like - it doesn't need to be a selected column - just make up what you need.
Looks a bit complicated with mysql, but you can use PHP for it. Use recursive function. This will be easy to handle.
here is a function from code bank. It simply creates a unorder list tree. You can modify it to suit your requirments
function output_lis_pages($parentID = 0)
{
$stack = array(); //create a stack for our <li>'s
$arr = array();
$sql = "select pageid, pagetitle, pagelink, parentid
from pages
where parentid = $parentID
order by orderid";
$crs = mysql_query($sql);
if(mysql_num_rows($crs)==0)
{
// no child menu exists for this page
return false;
}
else
{
while($crow = mysql_fetch_array($crs))
{
$arr [] = array(
'pagetitle'=> stripslashes($crow["pagetitle"]),
'pagelink'=> $crow["pagelink"],
'parentid'=>$crow["parentid"],
'pageid'=>$crow["pageid"]
);
}
}
foreach($arr as $a)
{
$str = '';
//if the item's parent matches the parentID we're outputting...
if($a['parentid']==$parentID)
{
if($a['pagelink']=="")
$tmplink = "page.php?pageid=".$a['pageid'];
else
$tmplink = $a['pagelink'];
$str.='<li>'.$a['pagetitle']."";
$subStr = output_lis_pages($a['pageid']);
if($subStr){
$str.="\n".'<ul>'.$subStr.'</ul>'."\n";
}
$str.='</li>'."\n";
$stack[] = $str;
}
}
//If we have <li>'s return a string
if(count($stack)>0)
{
return join("\n",$stack);
}
//If no <li>'s in the stack, return false
return false;
}
SELECT *
, CASE WHEN parent_id = 1 THEN id ELSE parent_id END AS sort_level
FROM questions
WHERE parent_id = 1
OR parent_id
IN (
SELECT id
FROM questions
WHERE parent_id = 1
AND parent_id != id
)
ORDER BY sort_level
, id
You've run into the old bugbear of relational database systems. They aren't fun to work with when your data is hierarchic. You have the issue of trying to produce what is really a particular walk of a graph from database records. That is tough without recursive features in your SQL dialect. Here is a link that might help:
http://explainextended.com/2009/03/17/hierarchical-queries-in-mysql/
See also, on StackOverflow: What are the options for storing hierarchical data in a relational database?
After a week of trying i could not get it to work with the query, so i decided just to do it in PHP, this will also reduce load off the MySQL engine. Here is my php for anyone that wishes to reference it.
$question_id = $database->escape_string($question_id); //escape input
$q = "SELECT * FROM questions WHERE parent_id = $question_id OR parent_id IN (SELECT id FROM questions WHERE parent_id = $question_id AND parent_id != id) ORDER BY parent_id , id";
$database->dbquery($q);//query the DB
while($row = $database->result->fetch_assoc()){//Process results to standard array.
//other irrelevant stuff happens here
$unsorted[] = $row;
}
$question = array_shift($unsorted);//take the question off the array
$sorted[] = $question;//add it to the start of the sorted array
$qusetion_id = $question['id'];
foreach($unsorted as $row){//this creates a multidimensional hierarchy of the answers->comments
if($row['parent_id'] == $question_id){//if its an answer
$sorted_multi[$row['id']] = array();//create a new answer sub-array
$sorted_multi[$row['id']][] = $row;//append it
}else{
$sorted_multi[$row['parent_id']][] = $row;//append the answer to the correct sub-array
}
}
foreach($sorted_multi as $temp){//converts the multidimensional into a single dimension appending it to the sorted array.
foreach($temp as $row){
$sorted[] = $row;
}
}
Tedious yes, but it works out better in the end because of other unforeseen processing that needs to be done post-mysql.
Thanks for all the responses though :):):)
looking into your question and reading your comment - "i need to get ONE specific question, with ALL the answers and comments", I think you are looking to show every question followed by its answer followed by its comments. Right?
And if so, this is your query:
SELECT `id`,
(CASE
WHEN `entry_type` = 'question' THEN CONCAT(`id`, '-', `parent_id`)
WHEN `entry_type` = 'answer' THEN CONCAT(`id`, '-', `parent_id`)
WHEN `entry_type` = 'comment' THEN CONCAT(`parent_id`, '-', `id`)
END) `sort_order`,
`entry_type`, `entry_content`
FROM `questions`
ORDER BY `sort_order`;
The above query will give you every question, followed by its first answer, followed by the comments to its first answer; then the second answer, followed by the comments to the second answer and so on.
So for the INSERTs that you had given, this will be the output:
+----+------------+------------+--------------------------+
| id | sort_order | entry_type | entry_content |
+----+------------+------------+--------------------------+
| 1 | 1-1 | question | How do I does SQL? |
| 2 | 2-1 | answer | Easy, you eat cheese! |
| 3 | 2-3 | comment | WTF are you on noobass?! |
| 6 | 2-6 | comment | 3 |
| 4 | 4-1 | answer | blah |
| 5 | 4-5 | comment | blah2 |
+----+------------+------------+--------------------------+
Hope it helps.
EDIT: Updated query to fetch answers and comments for only ONE question
SELECT `id`,
(CASE
WHEN (`entry_type` IN ('question', 'answer')) THEN `id`
WHEN `entry_type` = 'comment' THEN `parent_id`
END) `sort_order_1`,
(CASE
WHEN (`entry_type` IN ('question', 'answer')) THEN `parent_id`
WHEN `entry_type` = 'comment' THEN `id`
END) `sort_order_2`,
(CASE
WHEN (`entry_type` IN ('question', 'answer')) THEN `parent_id`
WHEN `entry_type` = 'comment' THEN (SELECT `Q1`.`parent_id` FROM `questions` `Q1` WHERE `Q1`.`id` = `Q`.`parent_id`)
END) `question_id`,
`entry_type`, `entry_content`
FROM `questions` `Q`
HAVING `question_id` = 1
ORDER BY `sort_order_1`, `sort_order_2`;
OUTPUT:
+----+--------------+--------------+-------------+------------+--------------------------+
| id | sort_order_1 | sort_order_2 | question_id | entry_type | entry_content |
+----+--------------+--------------+-------------+------------+--------------------------+
| 1 | 1 | 1 | 1 | question | How do I does SQL? |
| 2 | 2 | 1 | 1 | answer | Easy, you eat cheese! |
| 3 | 2 | 3 | 1 | comment | WTF are you on noobass?! |
| 6 | 2 | 6 | 1 | comment | 3 |
| 4 | 4 | 1 | 1 | answer | blah |
| 5 | 4 | 5 | 1 | comment | blah2 |
+----+--------------+--------------+-------------+------------+--------------------------+
You can change the HAVING part to fetch answers and comments for a specific question. Hope this helps!
EDIT 2: another possible implementation might be (but I think it might have some performance implications for large tables):
SELECT `a`.`id` AS `question_id`, `a`.`entry_content` AS `question`,
`b`.`id` AS `answer_id`, `b`.`entry_content` AS `answer`,
`c`.`id` AS `comment_id`, `c`.`entry_content` AS `comment`
FROM `questions` `a`
LEFT JOIN `questions` `b` ON (`a`.`id` = `b`.`parent_id` AND `b`.`entry_type` = 'answer')
LEFT JOIN `questions` `c` ON (`b`.`id` = `c`.`parent_id` AND `c`.`entry_type` = 'comment')
WHERE `a`.`entry_type` = 'question'
AND `a`.`id` = 1
ORDER BY `a`.`id`, `b`.`id`, `c`.`id`;
OUTPUT:
+----+--------------------+------+-----------------------+------+--------------------------+
| id | question | id | answer | id | comment |
+----+--------------------+------+-----------------------+------+--------------------------+
| 1 | How do I does SQL? | 2 | Easy, you eat cheese! | 3 | WTF are you on noobass?! |
| 1 | How do I does SQL? | 2 | Easy, you eat cheese! | 6 | 3 |
| 1 | How do I does SQL? | 4 | blah | 5 | blah2 |
+----+--------------------+------+-----------------------+------+--------------------------+
Simply use the "ORDER BY" clause to select the ordering you want!
SELECT *
FROM questions
WHERE parent_id =1
OR parent_id
IN (
SELECT id
FROM questions
WHERE parent_id =1
AND parent_id != id
)
ORDER BY Parent_id , id
Related
I have the following tables (minified for the sake of simplicity):
CREATE TABLE IF NOT EXISTS `product_bundles` (
bundle_id int AUTO_INCREMENT PRIMARY KEY,
-- More columns here for bundle attributes
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `product_bundle_parts` (
`part_id` int AUTO_INCREMENT PRIMARY KEY,
`bundle_id` int NOT NULL,
`sku` varchar(255) NOT NULL,
-- More columns here for product attributes
KEY `bundle_id` (`bundle_id`),
KEY `sku` (`sku`)
) ENGINE=InnoDB;
CREATE TABLE IF NOT EXISTS `products` (
`product_id` mediumint(8) AUTO_INCREMENT PRIMARY KEY,
`sku` varchar(64) NOT NULL DEFAULT '',
`status` char(1) NOT NULL default 'A',
-- More columns here for product attributes
KEY (`sku`),
) ENGINE=InnoDB;
And I want to show only the 'product bundles' that are currently completely in stock and defined in the database (since these get retrieved from a third party vendor, there is no guarantee the SKU is defined). So I figured I'd need an anti-join to retrieve it accordingly:
SELECT SQL_CALC_FOUND_ROWS *
FROM product_bundles AS bundles
WHERE 1
AND NOT EXISTS (
SELECT *
FROM product_bundle_parts AS parts
LEFT JOIN products AS products ON parts.sku = products.sku
WHERE parts.bundle_id = bundles.bundle_id
AND products.status = 'A'
AND products.product_id IS NULL
)
-- placeholder for other dynamic conditions for e.g. sorting
LIMIT 0, 24
Now, I sincerely thought this would filter out the products by status, however, that seems not to be the case. I then changed one thing up a bit, and the query never finished (although I believe it to be correct):
SELECT SQL_CALC_FOUND_ROWS *
FROM product_bundles AS bundles
WHERE 1
AND NOT EXISTS (
SELECT *
FROM product_bundle_parts AS parts
LEFT JOIN products AS products ON parts.sku = products.sku
AND products.status = 'A'
WHERE parts.bundle_id = bundles.bundle_id
AND products.product_id IS NULL
)
-- placeholder for other dynamic conditions for e.g. sorting
LIMIT 0, 24
Example data:
product_bundles
bundle_id | etc.
1 |
2 |
3 |
product_bundle_parts
part_id | bundle_id | sku
1 | 1 | 'sku11'
2 | 1 | 'sku22'
3 | 1 | 'sku33'
4 | 1 | 'sku44'
5 | 2 | 'sku55'
6 | 2 | 'sku66'
7 | 3 | 'sku77'
8 | 3 | 'sku88'
products
product_id | sku | status
101 | 'sku11' | 'A'
102 | 'sku22' | 'A'
103 | 'sku33' | 'A'
104 | 'sku44' | 'A'
105 | 'sku55' | 'D'
106 | 'sku66' | 'A'
107 | 'sku77' | 'A'
108 | 'sku99' | 'A'
Example result: Since the product status of product #105 is 'D' and 'sku88' from part #8 was not found:
bundle_id | etc.
1 |
I am running Server version: 10.3.25-MariaDB-0ubuntu0.20.04.1 Ubuntu 20.04
So there are a few questions I have.
Why does the first query not filter out products that do not have the status A.
Why does the second query not finish?
Are there alternative ways of achieving the same thing in a more efficient matter, as this looks rather cumbersome.
First of all, I've read that SQL_CALC_FOUND_ROWS * is much slower than running two separate query (COUNT(*) and then SELECT * or, if you make your query inside another programming language, like PHP, executing the SELECT * and then count the number of rows of the result set)
Second: your first query returns all the boundles that doesn't have ANY active products, while you need the boundles with ALL products active.
I'd change it in the following:
SELECT SQL_CALC_FOUND_ROWS *
FROM product_bundles AS bundles
WHERE NOT EXISTS (
SELECT 'x'
FROM product_bundle_parts AS parts
LEFT JOIN products ON (parts.sku = products.sku)
WHERE parts.bundle_id = bundles.bundle_id
AND COALESCE(products.status, 'X') != 'A'
)
-- placeholder for other dynamic conditions for e.g. sorting
LIMIT 0, 24
I changed the products.status = 'A' in products.status != 'A': in this way the query will return all the boundles that DOESN'T have inactive products (I also removed the condition AND products.product_id IS NULL because it should have been in OR, but with a loss in performance).
You can see my solution in SQLFiddle.
Finally, to know why your second query doesn't end, you should check the structure of your tables and how they are indexed. Executing an Explain on the query could help you to find eventual issues on the structure. Just put the keyword EXPLAIN before the SELECT and you'll have your "report" (EXPLAIN SELECT * ....).
I have a table with the structure
CREATE TABLE `old_reminder` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`applicant_id` int(11) NOT NULL,
`date` datetime NOT NULL,
`type` enum('payment_15_min','payment_1_day','payment_3_day') NOT NULL DEFAULT 'payment_15_min',
PRIMARY KEY (`id`)
)
and I want to migrate its data to another table with the structure
CREATE TABLE `new_reminders` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`payment_reminder_1_count` int(11) DEFAULT NULL,
`payment_reminder_1_date` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
)
basically multiple rows for each user_id will be converted into 1 row with all the enum values as columns.
I have tried the following but it is updating only 1 row
UPDATE reminders
INNER JOIN old_reminder AS `old`
ON user_id = old.applicant_id
SET new_reminder_1_date = IF(old.type = 'payment_15_min' OR old.type = 'payment_1_day' OR old.type = 'payment_3_day', old.date, '2018-01-01 00:00:00'),
payment_reminder_1_count = IF(old.type = 'payment_15_min' OR old.type = 'payment_1_day' OR old.type = 'payment_3_day',
CASE
WHEN old.type = 'payment_15_min' THEN 1
WHEN old.type = 'payment_1_day' THEN payment_reminder_1_count + 1
WHEN old.type = 'payment_3_day' THEN payment_reminder_1_count + 1 END, 0)
WHERE applicant_id = 123;
If you model looks like this
truncate table old_reminder;
insert into old_reminder (applicant_id,date,type) values (123,'2018-01-01','payment_15_min');
insert into old_reminder (applicant_id,date,type) values (123,'2018-01-02','payment_1_day');
truncate table new_reminders;
insert into new_reminders(user_id,payment_reminder_1_date) values (123,'2016-01-01');
Then a simplified version of your query
update new_reminders n join (select * from old_reminder order by applicant_id,`date`) old on old.applicant_id = n.user_id
SET
enumstr = old.type,
payment_reminder_1_date =
case
when old.type in ('payment_15_min','payment_1_day','payment_3_day') then old.date
else '2017-01-01'
end
WHERE applicant_id = 123;
Produces this result which I think is what you mean by it is updating only 1 row
+----+---------+--------------------------+-------------------------+----------------+
| id | user_id | payment_reminder_1_count | payment_reminder_1_date | enumstr |
+----+---------+--------------------------+-------------------------+----------------+
| 1 | 123 | NULL | 2018-01-01 00:00:00 | payment_15_min |
+----+---------+--------------------------+-------------------------+----------------+
1 row in set (0.00 sec)
The where clause is evaluated last in this query and is free to choose which one it wants from the result set - in this case always the first.
if the query is changed to test test the payment_reminder_1_date
update new_reminders n join (select * from old_reminder order by applicant_id,`date`) old on old.applicant_id = n.user_id
SET
enumstr = old.type,
payment_reminder_1_date =
case
when old.type in ('payment_15_min','payment_1_day','payment_3_day') then old.date
else '2017-01-01'
end
WHERE applicant_id = 123 and n.payment_reminder_1_date <> old.`date`
Then you get something more like what you hope for
+----+---------+--------------------------+-------------------------+---------------+
| id | user_id | payment_reminder_1_count | payment_reminder_1_date | enumstr |
+----+---------+--------------------------+-------------------------+---------------+
| 1 | 123 | NULL | 2018-01-02 00:00:00 | payment_1_day |
+----+---------+--------------------------+-------------------------+---------------+
1 row in set (0.00 sec)
But I don't think this is actually what you are looking for and I would suggest a cursor might be the way forward.
I have a forum and I would like to see the latest topics with the author's name and the last user who answered
Table Topic (forum)
| idTopic | IdParent | User | Title | Text |
--------------------------------------------------------
| 1 | 0 | Max | Help! | i need somebody |
--------------------------------------------------------
| 2 | 1 | Leo | | What?! |
Query:
SELECT
Question.*,
Response.User AS LastResponseUser
FROM Topic AS Question
LEFT JOIN (
SELECT User, IdParent
FROM Topic
ORDER BY idTopic DESC
) AS Response
ON ( Response.IdParent = Question.idTopic )
WHERE Question.IdParent = 0
GROUP BY Question.idTopic
ORDER BY Question.idTopic DESC
Output:
| idTopic | IdParent | User | Title | Text | LastResponseUser |
---------------------------------------------------------------------------
| 1 | 0 | Max | Help! | i need somebody | Leo |
---------------------------------------------------------------------------
Example:
http://sqlfiddle.com/#!2/22f72/4
The query works, but is very slow (more or less 0.90 seconds over 25'000 record).
How can I make it faster?
UPDATE
comparison between the proposed solutions
http://sqlfiddle.com/#!2/94068/22
If using your current schema, I'd recommend adding indexes (particularly a clustered index (primary key)) and simplifying your SQL to let mySQL do the work of optimising the statement, rather than forcing it to run a subquery, sort the results, then run the main query.
CREATE TABLE Topic (
idTopic INT
,IdParent INT
,User VARCHAR(100)
,Title VARCHAR(255)
,Text VARCHAR(255)
,CONSTRAINT Topic_PK PRIMARY KEY (idTopic)
,CONSTRAINT Topic_idTopic_UK UNIQUE (idTopic)
,INDEX Topic_idParentIdTopic_IX (idParent, idTopic)
);
INSERT INTO Topic (idTopic, IdParent, User, Title, Text) VALUES
(1, 0, 'Max', 'Help!', 'i need somebody'),
(2, 1, 'Leo', '', 'What!?');
SELECT Question.*
, Response.User AS LastResponseUser
FROM Topic AS Question
LEFT JOIN Topic AS Response
ON Response.IdParent = Question.idTopic
WHERE Question.IdParent = 0
order by Question.idTopic
;
http://sqlfiddle.com/#!2/7f1bc/1
Update
In the comments you mentioned you only want the most recent response. For that, try this:
SELECT Question.*
, Response.User AS LastResponseUser
FROM Topic AS Question
LEFT JOIN (
select a.user, a.idParent
from Topic as a
left join Topic as b
on b.idParent = a.idParent
and b.idTopic > a.idTopic
where b.idTopic is null
) AS Response
ON Response.IdParent = Question.idTopic
WHERE Question.IdParent = 0
order by Question.idTopic
;
http://sqlfiddle.com/#!2/7f1bc/3
Assuming the highest IDTopic is the last responses user...
and assuming you want to return topics without responses...
Select A.IDTopic, A.IDParent, A.User, A.Title, A.Text,
case when b.User is null then 'No Response' else B.User end as LastReponseUser
FROM topic A
LEFT JOIN Topic B
on A.IdTopic = B.IDParent
and B.IDTopic = (Select max(IDTopic) from Topic
where IDParent=B.IDParent group by IDParent)
WHERE A.IDParent =0
Sorry for the bizarre title, isn't easy to summarize what I want to achieve. My goal is to have a general table structure to store "any" kind of data, and to make it... describe itself, to get a custom table as output of the query.
I have a table with this structure:
id (INT)
idItem (INT)
fieldTitle (VARCHAR)
field (VARCHAR)
value_label (VARCHAR)
value_text (TEXT)
value_bool (TINYINT)
value_int (INT)
value_float (FLOAT)
An example of the data inside the table:
id idItem fieldTitle field value_label value_text value_bool value_int value_float
--------------------------------------------------------------------------------------------------------------
1 15 title value_label My Product One NULL NULL NULL NULL
2 15 description value_text NULL Very good product... NULL NULL NULL
3 15 visible value_bool NULL NULL 1 NULL NULL
4 15 price value_float NULL NULL NULL NULL 19.9
5 16 title value_label My Product Two NULL NULL NULL NULL
6 16 description value_text NULL Just an hidden product. NULL NULL NULL
7 16 visible value_bool NULL NULL 0 NULL NULL
8 16 price value_float NULL NULL NULL NULL 30
I want to end having something like this:
idItem (INT)
title (VARCHAR)
description (TEXT)
visible (TINYINT)
price (FLOAT)
Example of the output data:
idItem title description visible price
15 My Product One Very good product... 1 19.9
16 My Product Two Just an hidden product. 0 30
Although I don't agree with this kind of schemas (see my comment below your question) you can give this a try:
select idItem,
max(if(fieldTitle = 'title', value_label, null)) Title,
max(if(fieldTitle = 'description', value_text, null)) Description,
max(if(fieldTitle = 'visible', value_bool, null)) Visible,
max(if(fieldTitle = 'price', value_float, null)) Price
from t
group by idItem
This results in:
+--------+----------------+-------------------------+---------+-------+
| IDITEM | TITLE | DESCRIPTION | VISIBLE | PRICE |
+--------+----------------+-------------------------+---------+-------+
| 15 | My_Product_One | Very_good_product... | 1 | 19.9 |
| 16 | My_Product_Two | Just_an_hidden_product. | 0 | 30 |
+--------+----------------+-------------------------+---------+-------+
Adding more fields is straight forward. Let me know if you have ay issues with it.
Firstly, I agree with the views from Michael and Mosty. Why do you prefer to have a general table for storing all data rather than having specific tables? A table structure like below will keep your data better organised and your queries simpler.
id INT
title VARCHAR(255)
description TEXT
visible TINYINT
price DECIMAL(5,2)
Secondly, assuming that you work with your structure, the below query might help with retrieving data:
SELECT `t`.`idItem`, `t`.`value_label` AS `title`, `t1`.`value_text` AS `description`, `t2`.`value_bool` AS `visible`, `t3`.`value_float` AS `price`
FROM `your_table` `t`
INNER JOIN `your_table` `t1` ON `t`.`idItem` = `t1`.`idItem` AND `t1`.`fieldTitle` = 'description'
INNER JOIN `your_table` `t2` ON `t`.`idItem` = `t2`.`idItem` AND `t2`.`fieldTitle` = 'visible'
INNER JOIN `your_table` `t3` ON `t`.`idItem` = `t3`.`idItem` AND `t3`.`fieldTitle` = 'price'
WHERE `t`.`fieldTitle` = 'title';
Hope it helps.
I have a table with name-value pairs and additional attribute. The same name can have more than one value. If that happens I want to return the row which has a higher attribute value.
Table:
ID | name | value | attribute
1 | set1 | 1 | 0
2 | set2 | 2 | 0
3 | set3 | 3 | 0
4 | set1 | 4 | 1
Desired results of query:
name | value
set2 | 2
set3 | 3
set1 | 4
What is the best performing sql query to get the desired results?
the best performing query would be as follows:
select
s.set_id,
s.name as set_name,
a.attrib_id,
a.name as attrib_name,
sav.value
from
sets s
inner join set_attribute_values sav on
sav.set_id = s.set_id and sav.attrib_id = s.max_attrib_id
inner join attributes a on sav.attrib_id = a.attrib_id
order by
s.set_id;
+--------+----------+-----------+-------------+-------+
| set_id | set_name | attrib_id | attrib_name | value |
+--------+----------+-----------+-------------+-------+
| 1 | set1 | 3 | attrib3 | 20 |
| 2 | set2 | 0 | attrib0 | 10 |
| 3 | set3 | 0 | attrib0 | 10 |
| 4 | set4 | 4 | attrib4 | 10 |
| 5 | set5 | 2 | attrib2 | 10 |
+--------+----------+-----------+-------------+-------+
obviously for this to work you're gonna also have to normalise your design and implement a simple trigger:
drop table if exists attributes;
create table attributes
(
attrib_id smallint unsigned not null primary key,
name varchar(255) unique not null
)
engine=innodb;
drop table if exists sets;
create table sets
(
set_id smallint unsigned not null auto_increment primary key,
name varchar(255) unique not null,
max_attrib_id smallint unsigned not null default 0,
key (max_attrib_id)
)
engine=innodb;
drop table if exists set_attribute_values;
create table set_attribute_values
(
set_id smallint unsigned not null,
attrib_id smallint unsigned not null,
value int unsigned not null default 0,
primary key (set_id, attrib_id)
)
engine=innodb;
delimiter #
create trigger set_attribute_values_before_ins_trig
before insert on set_attribute_values
for each row
begin
update sets set max_attrib_id = new.attrib_id
where set_id = new.set_id and max_attrib_id < new.attrib_id;
end#
delimiter ;
insert into attributes values (0,'attrib0'),(1,'attrib1'),(2,'attrib2'),(3,'attrib3'),(4,'attrib4');
insert into sets (name) values ('set1'),('set2'),('set3'),('set4'),('set5');
insert into set_attribute_values values
(1,0,10),(1,3,20),(1,1,30),
(2,0,10),
(3,0,10),
(4,4,10),(4,2,20),
(5,2,10);
This solution will probably perform the best:
Select ...
From Table As T
Left Join Table As T2
On T2.name = T.name
And T2.attribute > T1.attribute
Where T2.ID Is Null
Another solution which may not perform as well (you would need to evaluate against your data):
Select ...
From Table As T
Where Not Exists (
Select 1
From Table As T2
Where T2.name = T.name
And T2.attribute > T.attribute
)
select name,max(value)
from table
group by name
SELECT name, value
FROM (SELECT name, value, attribute
FROM table_name
ORDER BY attribute DESC) AS t
GROUP BY name;
There is no easy way to do this.
A similar question was asked here.
Edit: Here's a suggestion:
SELECT `name`,`value` FROM `mytable` ORDER BY `name`,`attribute` DESC
This isn't quite what you asked for, but it'll at least give you the higher attribute values first, and you can ignore the rest.
Edit again: Another suggestion:
If you know that value is a positive integer, you can do this. It's yucky, but it'll work.
SELECT `name`,CAST (GROUP_CONCAT(`value` ORDER by `attribute` DESC) as UNSIGNED) FROM `mytable` GROUP BY `name`
To include negative integers you could change UNSIGNED to SIGNED.
Might want to benchmark all these options, here's another one.
SELECT t1.name, t1.value
FROM temp t1
WHERE t1.attribute IN (
SELECT MAX(t2.attribute)
FROM temp t2
WHERE t2.name = t1.name);
How about:
SELECT ID, name, value, attribute
FROM table A
WHERE A.attribute = (SELECT MAX(B.attribute) FROM table B WHERE B.NAME = A.NAME);
Edit: Seems like someones said the same already.
Did not benchmark them, but here is how it is doable:
TableName = temm
1) Row with maximum value of attribute :
select t.name, t.value
from (
select name, max(attribute) as maxattr
from temm group by name
) as x inner join temm as t on t.name = x.name and t.attribute = x.maxattr;
2) Top N rows with maximum attribute value :
select name, value
from temm
where (
select count(*) from temm as n
where n.name = temm.name and n.attribute > temm.attribute
) < 1 ; /* 1 can be changed to 2,3,4 ..N to get N rows */