MySQL: standard approach VS set-based approach for multi-row results - php

Following up on this question, there is problem with the standard approach or a recursive approach in the case below.
For instance, I want to return the page with the same parent_id and some pages have multiple row contents,
page table
page_id page_title parent_id
1 a 1
2 aa 1
3 ab 1
4 ac 1
content table
content_id content_text
1 text one
2 text two
3 text one aa
4 text two aa
5 text one ab
6 text one ac
content structure table
page_id content_id order_in_page
1 1 1
1 2 2
2 3 1
2 4 2
3 5 1
4 6 1
The standard approach,
SELECT
p.*,
c.*,
x.*
FROM pages AS p
LEFT JOIN pages_structures AS x
ON x.page_id = p.page_id
LEFT JOIN pages_contents AS c
ON c.content_id = x.content_id
WHERE p.parent_id = '1'
AND p.page_id != '1'
result (it lists the row as 4 items),
page_id page_title parent_id content_text order_in_page
2 aa 1 text one aa 1
2 aa 1 text two aa 2
3 ab 1 text one ab 1
4 ac 1 text one ac 1
As you can notice that there are two rows with page_id 2 and one row for each 3 and 4. How do you display the data into HTML with the standard approach like below (as suggested in one of the answer in the previous question)?
echo $page[0]['page_title'];
echo $page[0]['content_text'];
But with set-based one, I can do it with this,
SELECT
page_id,
page_title,
MAX(IF(order_in_page = 1, content_text, NULL)) AS content_1,
MAX(IF(order_in_page = 2, content_text, NULL)) AS content_2,
.
.
.
FROM
pages AS p LEFT JOIN
pages_structures AS x ON x.page_id = p.page_id LEFT JOIN
pages_contents AS c ON c.content_id = x.content_id
WHERE
p.parent_id = '1'
AND
p.page_id != '1'
GROUP BY page_id
in PHP,
foreach($items as $item)
{
echo '<li><h3>'.$item['page_title'].'</h3>';
echo '<p>'.$item['content_1'].'</p>';
echo '<p>'.$item['content_2'].'</p></li>';
}
the HTML (it lists the row as 3 items which is correct),
<li>
<h3>aa</h3>
<p>text one aa</p>
<p>text two aa</p>
</li>
<li>
<h3>ab</h3>
<p>text one ab</p>
<p></p>
</li>
<li>
<h3>ac</h3>
<p>text one ac</p>
<p></p>
</li>
Hope this makes sense!

with the standard approach you just keep track of the current page in a variable, and when the page changes, you roll to a new list item.

Related

mysqlCount not working with join,Showing wrong result

I have two tables"shopinfo" and "rating" and i am trying to count review with Join but showing me wrong result
Here is my first table "shopInfo"
id shopOpen shopname
1 2 abc
2 1 xyz
3 2 dnu
4 2 rfy
Here is my table "rating"
id shopId rating review
1 2 3 Lorem Ipsum
2 2 4 Lorem Ipsum
3 4 5 Lorem Ipsum
4 2 1 Lorem Ipsum
And here is my code which showing me wrong result in review count (showing me 6 , should be 3),
Where i am wrong ?
SELECT si.shopOpen, COUNT(r.review) as reviewCount, AVG(r.rating) AS AvgRating
FROM shopInfo si
LEFT JOIN rating r ON r.shopId=si.id
WHERE si.shopId = '2'
you are missing group by. when you use aggregated functions like sum(), avg() you have to group by non-aggregated column. In your case it is si.shopOpen
Here is the fiddle link provided by #VBoka
SELECT
si.shopOpen,
COUNT(r.review) as reviewCount,
AVG(r.rating) AS AvgRating
FROM shopInfo si
JOIN rating r ON r.shopId=si.id
WHERE si.shopId = '2'
group by
si.shopOpen

MySQL pulling content from 2 tables that is connected by a third table

I currently have 2 tables in a database I need to get information from, "content" and "type". These two tables are linked by a 3rd table name "typeMembers." This is the structure:
Table Content:
id content link date isPublished
1 content 1 link 1 3/13/91 1
2 content 2 link 2 3/18/91 1
3 content 3 link 3 3/22/91 1
Table type:
id name
1 Event
2 Page
3 Test
Table typeMember
id type_id content_id
1 1 1
2 2 1
3 3 1
4 1 2
5 1 3
Currently I have my query set up as:
//using PDO in PHP
q = $dbc->prepare(
"SELECT a.id, a.content,a.date,a.link, c.name
FROM content a
LEFT OUTER JOIN typeMember b
ON b.content_id = a.id
LEFT OUTER JOIN types c
ON b.type_id = c.id
WHERE a.isPublished = 1
ORDER BY a.date DESC"
);
$r = $q->execute();
When this is returned I am getting 1 row for each typeMember in the database instead of content. What am I structuring wrong?
Data I would like to be returned:
id content link date name
1 content 1 link 1 3/13/91 Event, Page, Test
2 content 2 link 2 3/18/91 Event
3 content 3 link 3 3/22/91 Event
How it is being returned
id content link date name
1 content 1 link 1 3/13/91 Event
1 content 1 link 1 3/13/91 Page
1 content 1 link 1 3/13/91 Test
2 content 2 link 2 3/18/91 Event
3 content 3 link 3 3/22/91 Event
Edit: filing out the data actually made me realize what is going on. There is a 1 to many relationship with content to type. Is there a way to get all the types in one query?
for get the name in the same row you can use group_Concat
SELECT a.id, a.content, a.date, a.link, group_concat(c.name )
FROM content a
LEFT JOIN typeMember b ON b.content_id = a.id
LEFT JOIN types c ON b.type_id = c.id
WHERE a.isPublished = 1
Group by a.id, a.content, a.date, a.link
ORDER BY a.date DESC

MYSQL select tag associated id's from same table

So I have a MYSQL table which stores a load of tags that are associated to one another like so:
id level upper_tag value
----------------------------------------------
1 0 0 Electric
2 0 0 Home
3 1 2 Gas Cooker
4 1 1 wire
5 2 4 superconductor_wire
6 0 0 Sexy stuff
7 2 3 cleaner
8 0 0 Sport
9 1 8 trainers
10 1 8 rugby
11 2 10 Nike
Basically I need to find the associated tags so if I call sport I want the tags: Sport, trainers, rugby and nike returned. If I chose the tag rugby I would get back rugby and nike.
I have had a think and am not sure if I am on the right lines as I am a MYSQL noob. here is my attempt but it does not give me the right answer:
SELECT * FROM tag t
WHERE t.`id` = (SELECT `upper_tag` FROM tag) AND t.`id` = 8
;
SQLFIDDLE
What you are looking for is a Nested set model - read through the Wiki article and see the example on how left/right values are assigned. Once you have those two columns added and indexed correctly, your queries will be as simple as
SELECT t.* FROM tag t
WHERE t.`left` >= X and t.`right` <= Y
Where X is the 'left' value and Y is the 'right' value of 'Sport' tag
You can combine the lookup an actual loading into one query
SELECT t.* FROM tag t
JOIN tag t ON t.`left` >= b.`left` AND t.`right` <= b.`right`
WHERE b.id = 8
Howerver you would need extra work to maintain (reindex as necessary) the integrity of these left/right values whenever you add/remove/move any tag.
if you want it across all levels, you need to use joins and unions. add one join per query for each deeper level.
select * from
(
select t1.id as parent, t2.* from tag t1
inner join tag t2 on t1.id=t2.parent_id
union
select t1.id as parent, t3.* from tag t1
inner join tag t2 on t1.id=t2.parent_id
inner join tag t3 on t2.id=t3.parent_id
) tag where tag.parent=1
SELECT * FROM tag t WHERE t.upper_tag=8 OR t.id=8
this should give you all tag with the value upper_tag=8 and id = 8
sport=8, rugby=upper_tag 8, trainer=upper_tag 8
Try like this
SELECT *
from tag AS t
WHERE (t.upper_tag IN(SELECT id from tag WHERE upper_tag=8))
OR (t.id = 8) OR (t.upper_tag = 8)

Shortest string within same path (branch)

I have a tree representation in mysql table based on id, depth, parent_id and path. Every root record within this table has a depth of 0, parent_id != null and path representation based on hex value of ID padded left with 0.
Every element of the tree is constructed by specifying depth = parent.depth + 1, path = parent.path + hex(id), parent_id = parent.id (pseudo code) for example:
id path depth parent_id assigned_user_id
------------------------------------------------------------
1 001 0 NULL NULL
2 002 0 NULL 1
3 001003 1 1 2
4 002004 1 2 1
5 001003005 2 3 2
6 001003005006 3 5 2
7 002004007 2 4 1
8 002004008 2 4 2
9 002004009 2 4 2
10 00200400800A 3 8 2
and so on...
The problem is how to get the records for specific user id limited to the shortest path in the same branch. For example for assigned_user_id = 2 retrive:
id path depth parent_id assigned_user_id
------------------------------------------------------------
3 001003 1 1 2
8 002004008 2 4 2
9 002004009 2 4 2
Instead of:
id path depth parent_id assigned_user_id
------------------------------------------------------------
3 001003 1 1 2
5 001003005 2 3 2
6 001003005006 3 5 2
8 002004008 2 4 2
9 002004009 2 4 2
10 00200400800A 3 8 2
SELECT t1.*
FROM atable t1
LEFT JOIN atable t2
ON t2.assigned_user_id = t1.assigned_user_id AND
t2.path = LEFT(t1.path, CHAR_LENGTH(t2.path)) AND
t2.id <> t1.id
WHERE t1.assigned_user_id = 2
AND t2.id IS NULL
If I get you right, it might be enough to exclude rows whose parent_id is among the ids selected. This is because if the parent and child is selected, they must be in the same branch. The parent's path will be shorter, therefore it's OK to exclude the child.
Something like:
SELECT *
FROM x
WHERE assigned_user_id = 2
AND parent_id NOT IN (SELECT id FROM x WHERE assigned_user_id = 2)
If you would have a tree like this (numbers are your assigned user ids):
A1 G2
/ \ / \
B2 C2 H2 I2
| \ | | \
D2 E2 L1 J2 K2
|
M2
B2, C2, G2 and M2 would be selected. I'm still not sure if this was your intention, though.
I would try something like this:
SELECT * FROM PATHS WHERE ASSIGNED_USER_ID = 2
AND NOT PARENT_ID IN (SELECT ID FROM PATHS WHERE ASSIGNED_USER_ID = 2)
Basically the idea is to select top parent nodes for the given user.
Idea behind this: B is shorter than A if A starts with B. Maybe there's something better than LIKE to do this "begins with".
SELECT a.* FROM node AS a
WHERE a.assigned_user_id = ?
AND NOT EXIST
(SELECT * FROM node AS b
WHERE b.assigned_user_id = ?
AND LENGTH(a.path) > LENGTH(b.path)
AND a.path LIKE CONCAT(b.path, '%') )
Both ? are mapped to the desired user id.
EDIT
Forgot to include the assigned_user_id. Changed the code.
2nd EDIT
Changed code to avoid the case of b=a.
Have you tried something like this?
select child.assigned_user_id, child.id
from node as child
left join node as parent
on child.path like CONCAT(parent.path, '%')
and child.assigned_user_id = parent.assigned_user_id
and child.id <> parent.id
group by child.assigned_user_id, child.id
having max(parent.id is null) = true
(Not sure it'll work exactly as above, but basically: to left join on the path in order to extract the full list of parents, and then to aggregate in such a way that you only keep the nodes without any parents when grouped by assigned_user_id.)

Help with limiting a joined mysql database query

I have written a query which returns all records with some many-to-many joins correctly for the entire set or an individual article using WHERE a.id = ?
SELECT a.id, date_added, title, content, category_id, person_id, organization_id, c.name AS category_name, firstname, lastname, o.name AS organization_name
FROM articles AS a
LEFT OUTER JOIN articles_categories AS ac ON a.id=ac.article_id
LEFT OUTER JOIN categories AS c ON c.id=ac.category_id
LEFT OUTER JOIN articles_people AS ap ON a.id=ap.article_id
LEFT OUTER JOIN people AS p ON p.id=ap.person_id
LEFT OUTER JOIN articles_organizations AS ao ON a.id=ao.article_id
LEFT OUTER JOIN organizations AS o ON o.id=ao.organization_id
ORDER BY date_added
BUT!
I've hit a brick wall trying to work out how to limit the articles to a specific number of IDs, for working with pagination.
I'm ideally trying to use as simple and clear SQL statements as possible because I'm using the codeigniter framework with their active record class.
http://codeigniter.com/user_guide/database/active_record.html
Would really appreciate some help as I don't want to revert to using multiple queries for this as I've tried to reduce it down to a single query for database efficiency.
Have search around and tried some alternatives but nothing seems to work. Many thanks!
For example the results I return are like this
---------------------------------------------------------------------
id title category_id person_id organization_id
---------------------------------------------------------------------
1 test 1 1 1
1 test 2 1 1
1 test 1 2 1
1 test 1 1 2
1 test 5 1 1
1 test 8 1 1
1 test 1 4 1
1 test 1 4 2
1 test 1 1 1
2 test 2 2 1 1
2 test 2 1 2 1
2 test 2 1 1 2
2 test 2 5 1 1
2 test 2 8 1 1
2 test 2 1 4 1
2 test 2 1 4 2
I need the results like this so that I can create sub-arrays in the php like this:
$articles = $query->result_array();
$output = array();
foreach ($articles as $article) {
// set up article details
$article_id = $article['id'];
// add article details
$output[$article_id]['article_id'] = $article_id;
$output[$article_id]['date_added'] = $article['date_added'];
$output[$article_id]['title'] = $article['title'];
$output[$article_id]['content'] = $article['content'];
// set up people details and add people array with details if exists
if (isset($article['person_id'])) {
$person_id = $article['person_id'];
$output[$article_id]['people'][$person_id]['person_id'] = $person_id;
$output[$article_id]['people'][$person_id]['lastname'] = $article['lastname'];
$output[$article_id]['people'][$person_id]['firstname'] = $article['firstname'];
}
// set up organizations details and add organizations array with details if exists
if (isset($article['organization_id'])) {
$organization_id = $article['organization_id'];
$output[$article_id]['organizations'][$organization_id]['organization_id'] = $organization_id;
$output[$article_id]['organizations'][$organization_id]['organization_name'] = $article['organization_name'];
}
// set up categories details and add categories array with details if exists
if (isset($article['category_id'])) {
$category_id = $article['category_id'];
$output[$article_id]['categories'][$category_id]['category_id'] = $category_id;
$output[$article_id]['categories'][$category_id]['category_name'] = $article['category_name'];
}
}
But if I just use LIMIT (with offset etc) 1
the results I get are
---------------------------------------------------------------------
id title category_id person_id organization_id
---------------------------------------------------------------------
1 test 1 1 1
instead of
---------------------------------------------------------------------
id title category_id person_id organization_id
---------------------------------------------------------------------
1 test 1 1 1
1 test 2 1 1
1 test 1 2 1
1 test 1 1 2
1 test 5 1 1
1 test 8 1 1
1 test 1 4 1
1 test 1 4 2
1 test 1 1 1
which is my desired result.
OK, so finally I worked out how it is possible.
Thought i'd include it here in case anyone else has the same problem.
Changing this line
FROM articles AS a
to this
FROM (SELECT * FROM articles LIMIT 5,3) AS a
does what I wanted.
So, why don't you use OFFSET 0,10 and LIMIT *number_of_results* in the SQL Query? (if I understood the question)
Specific number of IDs... WHERE ID IN (2,4,6,8)... ?
Are you using codeigniter's pagination?
http://codeigniter.com/user_guide/libraries/pagination.html
You can easily limit the number of records that are being returned using the MySQL LIMIT clause. This can be achieved like the following with your sample query.
SELECT a.id, date_added, title, content, category_id, person_id, organization_id, c.name AS category_name, firstname, lastname, o.name AS organization_name
FROM articles AS a
LEFT OUTER JOIN articles_categories AS ac ON a.id=ac.article_id LEFT OUTER JOIN categories AS c ON c.id=ac.category_id
LEFT OUTER JOIN articles_people AS ap ON a.id=ap.article_id LEFT OUTER JOIN people AS p ON p.id=ap.person_id
LEFT OUTER JOIN articles_organizations AS ao ON a.id=ao.article_id LEFT OUTER JOIN organizations AS o ON o.id=ao.organization_id
ORDER BY date_added
LIMIT 10
Where 10 is the number of records you wish to display. The MySQL LIMIT clause allows you to specify a limit of the number of records and an initial offset. Like so:
LIMIT <offset>,<limit>
In your case <offset> would be the current page * the number of records on a page. <limit> would be the number of records you would like to display per page.

Categories