Mysql Select 1:n - mysql

I have two tables that relates 1:n
content
---------
- id
- title
- text
content_meta
-------------
- id
- content_id
- meta_key
- meta_value
A content can have multiple content_meta registers associated to it. Typically content_meta will contain the category, tags, descriptions and all that stuff, so I really don't know the number of registers a content will have.
What I want to accomplish is to take the content register and also all the related registers in content_meta in a single query.
I've tried the subselect approachment but seems that I can only get one register/column (¿?)
SELECT content.*, (
SELECT *
FROM content_meta
WHERE content_id = content.id
)
FROM content
This query complains that "Operand should contain 1 column(s)", so changing the '*' by for example meta_key clears the error, but returns a NULL for this subselect...
SELECT content.*, (
SELECT meta_key
FROM content_meta
WHERE content_id = content.id
)
FROM content
Can anybody show me where to go from here please?

Use:
SELECT c.*,
cm.*
FROM CONTENT c
JOIN CONTENT_META cm ON cm.content_id = c.id
That will only return CONTENT and related when there is a supporting record in the CONTENT_META table. If it's possible for a CONTENT record to not have any CONTENT_META data, use a LEFT JOIN instead:
SELECT c.*,
cm.* --these columns will be null if there is no supporting data
FROM CONTENT c
LEFT JOIN CONTENT_META cm ON cm.content_id = c.id
Followup Question -
it is now possible to group by content.id, but renaming the meta_key column with its own value and the value of this column the content of meta_value?
MySQL doesn't have PIVOT syntax - you have to use CASE statements:
SELECT c.id,
MAX(CASE WHEN cm.meta_key = 'A' THEN cm.meta_value ELSE NULL END) AS 'A',
MAX(CASE WHEN cm.meta_key = 'B' THEN cm.meta_value ELSE NULL END) AS 'B',
MAX(CASE WHEN cm.meta_key = 'C' THEN cm.meta_value ELSE NULL END) AS 'C'
FROM CONTENT c
JOIN CONTENT_META cm ON cm.content_id = c.id
GROUP BY c.id
You'll have to specify the meta_key for each one you want to appear in the resultset.

How about just doing a join on the content id column?

SELECT * FROM content
LEFT JOIN content_meta ON content.id = content_meta.content_id

Related

Nested MySQL selects issue within WordPress usermeta table

Having an issue with a complex MySql select statement and hoping for some pointers!
So I am using a number of plugins on top of WordPress with some interesting ways of storing data. The bits I'm concerned with are as following: There are some 'parent' accounts which have a number of child accounts. The parent to child relationship is stored in the usermeta table (user_id=user_id of child account, meta_value = parent_id, meta_key='parent'). Each of these child accounts can also complete a number of tasks. This is also stored in the usermeta table (user_id=user_id of child account, meta_value = complete_status, meta_key='task_id_'.task_id).
I'm trying to create a view where I get a list of each of these parent accounts, along with a few bits of information, then a few derived values from their children, including the average number of completed tasks by the children of each parent.
This is my MySQL statement, the part that is having the issue is the nested select:
SELECT
wp_parent_account_info_table.obj_id,
wp_parent_account_info_table.obj_type,
wp_parent_account_info_table.id,
wp_other_custom_table_info.created_at,
wp_other_custom_table_info.product_id,
(SELECT AVG(cc.rcount)
FROM (SELECT DISTINCT COUNT(*) as rcount
FROM wp_usermeta
WHERE meta_key LIKE 'task_id_%'
AND meta_value = 'complete'
AND wp_usermeta.user_id IN (SELECT DISTINCT user_id
FROM wp_usermeta
WHERE meta_key = 'parent'
AND meta_value = wp_parent_account_info_table.id
) AS sc
) AS cc
) AS a
FROM wp_parent_account_info_table
JOIN wp_other_custom_table_info
ON `wp_parent_account_info_table`.`obj_id`=`wp_other_custom_table_info`.`id`
INNER JOIN wp_another_custom_table_info
ON `wp_another_custom_table_info`.`subscription_id`=`wp_parent_account_info_table`.`obj_id` WHERE `wp_parent_account_info_table`.`status` = 'enabled'
AND `wp_parent_account_info_table`.`sub_accounts_available` <> '0'
AND `wp_other_custom_table_info`.`status` = 'active'
AND (`wp_another_custom_table_info`.`expires_at` > CONCAT(CURDATE(), ' 23:59:59')
OR `wp_another_custom_table_info`.`expires_at` = '0000-00-00 00:00:00')
LIMIT 0,30;
I've tried to make this readable, apologies for complexity. I didn't want to remove any parts incase they were relevant.
The statement works fine without the nested select. It also works (has no error) if I replace the most nested select with an array of IDs (so just putting: IN (1,2,3)). Is this something about me trying to get the parent ID from too far down?
This is the error:
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'AS sc) AS cc) AS a FROM wp_parent_account_info_table JOIN wp_other_custom_table_info ON'
Any pointers would be much appreciated.
Edit:
Along with the answer below which resolved this error, I also didn't have access to the id variable in the furthest nest (a new error!), so I split them into an extra column. Here is my final code:
SELECT
wp_parent_account_info_table.obj_id,
wp_parent_account_info_table.obj_type,
wp_parent_account_info_table.id,
wp_other_custom_table_info.created_at,
wp_other_custom_table_info.product_id,
(SELECT DISTINCT COUNT(*)
FROM wp_usermeta
WHERE meta_key = 'parent'
AND meta_value = wp_parent_account_info_table.id) as c,
(SELECT DISTINCT COUNT(*) as rcount
FROM wp_usermeta
WHERE meta_key LIKE 'task_id_%'
AND meta_value = 'complete'
AND wp_usermeta.user_id IN (SELECT DISTINCT user_id
FROM wp_usermeta
WHERE meta_key = 'parent'
AND meta_value = wp_parent_account_info_table.id
)
) AS a,
(SELECT a / c)
FROM wp_parent_account_info_table
JOIN wp_other_custom_table_info
ON `wp_parent_account_info_table`.`obj_id`=`wp_other_custom_table_info`.`id`
INNER JOIN wp_another_custom_table_info
ON `wp_another_custom_table_info`.`subscription_id`=`wp_parent_account_info_table`.`obj_id` WHERE `wp_parent_account_info_table`.`status` = 'enabled'
AND `wp_parent_account_info_table`.`sub_accounts_available` <> '0'
AND `wp_other_custom_table_info`.`status` = 'active'
AND (`wp_another_custom_table_info`.`expires_at` > CONCAT(CURDATE(), ' 23:59:59')
OR `wp_another_custom_table_info`.`expires_at` = '0000-00-00 00:00:00')
LIMIT 0,30;
The syntax error is related to your IN cluse ... the IN clause base on a subselect don't require a tablename alias then avoid the sc after the )
SELECT
wp_parent_account_info_table.obj_id,
wp_parent_account_info_table.obj_type,
wp_parent_account_info_table.id,
wp_other_custom_table_info.created_at,
wp_other_custom_table_info.product_id,
(SELECT AVG(cc.rcount)
FROM (SELECT DISTINCT COUNT(*) as rcount
FROM wp_usermeta
WHERE meta_key LIKE 'task_id_%'
AND meta_value = 'complete'
AND wp_usermeta.user_id IN (SELECT DISTINCT user_id
FROM wp_usermeta
WHERE meta_key = 'parent'
AND meta_value = wp_parent_account_info_table.id
)
) AS cc
) AS a
FROM wp_parent_account_info_table
JOIN wp_other_custom_table_info
ON `wp_parent_account_info_table`.`obj_id`=`wp_other_custom_table_info`.`id`
INNER JOIN wp_another_custom_table_info
ON `wp_another_custom_table_info`.`subscription_id`=`wp_parent_account_info_table`.`obj_id` WHERE `wp_parent_account_info_table`.`status` = 'enabled'
AND `wp_parent_account_info_table`.`sub_accounts_available` <> '0'
AND `wp_other_custom_table_info`.`status` = 'active'
AND (`wp_another_custom_table_info`.`expires_at` > CONCAT(CURDATE(), ' 23:59:59')
OR `wp_another_custom_table_info`.`expires_at` = '0000-00-00 00:00:00')
LIMIT 0,30;

SQL: LEFT JOIN and alias not working together

$query = "SELECT a.comment_user_id as main_id, a.comment_date as timestamp, a.comment_content as content, a.comment_link_id as link_unique, a.comment_id as status, NULL as url, b.user_login as ulogin, b.user_avatar_source as uavatar, c.link_title as ltitle, NULL as desc FROM kliqqi_comments as a WHERE comment_user_id IN ('$following2')
LEFT JOIN kliqqi_users as b ON a.comment_user_id = b.user_id
LEFT JOIN kliqqi_links as c ON a.comment_user_id = c.link_author
ORDER BY timestamp DESC LIMIT 10";
$result = mysqli_query($db_conx, $query);
$row = $result->fetch_array(MYSQLI_ASSOC);
Can anybody tell me what's wrong with the code? It is always returning this error:
Fatal error: Call to a member function fetch_assoc() on boolean
Boolean means this query is not getting executed due to some error in $query variable which I am unable to figure out.
$following is an array. kliqqi_comments alias a, kliqqi_users alias b, kliqqi_links alias c. I am storing all the other fields as alias too. There is no typo or any other silly mistake. I've checked it thoroughly.
UPDATE:
I'm updating this thread because my query actually has many parts and many users may find it helpful.
$query = "SELECT a.comment_user_id as main_id, a.comment_date as timestamp2, a.comment_content as content, a.comment_link_id as link_unique, a.comment_id as status, b.user_login as ulogin, b.user_avatar_source as uavatar, c.link_title as ltitle FROM kliqqi_comments a
LEFT JOIN kliqqi_users b ON a.comment_user_id = b.user_id
LEFT JOIN kliqqi_links c ON a.comment_link_id = c.link_id
WHERE comment_user_id IN ('$following')
UNION ALL
SELECT d.link_author as main_id, d.link_date as timestamp2, d.link_status as content, d.link_id as link_unique, NULL as status, e.user_login as ulogin, e.user_avatar_source as uavatar, d.link_title as ltitle FROM kliqqi_links d
LEFT JOIN kliqqi_users e ON d.link_author = e.user_id
WHERE link_author IN ('$following') AND link_status IN ('new','published')
UNION ALL
SELECT f.vote_user_id as main_id, f.vote_date as timestamp2, f.vote_value as content, f.vote_link_id as link_unique, NULL as status, g.user_login as ulogin, g.user_avatar_source as uavatar, h.link_title as ltitle FROM kliqqi_votes f
LEFT JOIN kliqqi_users g ON f.vote_user_id = g.user_id
LEFT JOIN kliqqi_links h ON f.vote_link_id = h.link_id
WHERE vote_user_id IN ('$following')
ORDER BY timestamp2 DESC LIMIT 30";
What does it do?
I've 3 tables: kliqqi_links, kliqqi_users, kliqqi_votes
UNION ALL
All of them have a timestamp field.
I wanted to fetch contents from these 3 tables combined in decreasing order of timestamp. And to do so, I used UNION ALL (UNION can also be used here but UNION has to run duplicate checks so it's better to avoid it if you can.). But UNION ALL works only when all of the tables have same number of fields. So, I created NULL elements for equating the numbers.
It is to be noted that there is no restriction of datatype for uniting respective fields. But since I had to use timestamp for sequence, I kept them together.
Alias
Since all the respective fields have different names in different tables, I used alias to avoid confusion. Without alias, results are stored in fields mentioned in first SELECT statement which would be a mess.
Multiple LEFT JOIN
Now, I wanted to grab some data from other tables for each SELECT query.
e.g. for kliqqi_comments (first SELECT statement), I wanted to grab user data for the person who made the comment from kliqqi_users plus I wanted to fetch the link where this comment was made from kliqqi_links table. So, I used left join with kliqqi_comments query where comment_user_id from kliqqi_comments equals to user_id from kliqqi_users and comment_link_id from kliqqi_comments equals link_id from kliqqi_links.
Notice that I managed to equate fields in all 3 statements for UNION ALL.
WHERE IN
$following is comma separated array to ensure that it returns result from the people user is following.
ORDER BY DESC, LIMIT
To order by timestamp and LIMIT output result.
That's it.
The where clauses should come after the join clauses, not before them. Additionally, desc and timestamp are reserved words. If you absolutely must use them as a column aliases, you need to escape them:
SELECT a.comment_user_id as main_id,
a.comment_date as `timestamp`, -- Notice the escaping
a.comment_content as content,
a.comment_link_id as link_unique,
a.comment_id as status,
NULL as url,
b.user_login as ulogin,
b.user_avatar_source as uavatar,
c.link_title as ltitle,
NULL as `desc` -- Notice the escaping
FROM kliqqi_comments as a
LEFT JOIN kliqqi_users as b ON a.comment_user_id = b.user_id
LEFT JOIN kliqqi_links as c ON a.comment_user_id = c.link_author
WHERE comment_user_id IN ('$following2') -- Where clause after the joins
ORDER BY `timestamp` DESC LIMIT 10";

MySql: how to make subquery and count all rows where id is the same in two tables

How to make a query which return values for specific ID's not for all.
SELECT content.id, count(case likes.type when 'p' then 1 else null end) as p
FROM content
JOIN likes on likes.content_id = content.id
this code returns:
ID p
1 18
but i want it:
ID p
1 12
2 4
3 2
Add a group by
SELECT content.id,
sum(likes.type = 'p') as p
FROM content
JOIN likes on likes.content_id = content.id
GROUP BY content.id
Then the aggregate functions (i.e. count()) are applied to the groups and not to the whole result.
You query would fail in most databases. The problem is that content.id is not summarized in the select but you are using an aggregation function.
This is a simple problem to fix:
SELECT content.id, count(case likes.type when 'p' then 1 else null end) as p
FROM content
JOIN likes on likes.content_id = content.id
GROUP BY content.id;
However, in general, you should be careful and always include all non-aggregated columns in the select in a group by.

How can I list all rows in table and count from multiple table that related with each rows?

I have made Q & A website that a questions can have tags in it and the user can subscribe to tags they want. So, I have create tag page for list all tags and count all questions and users who subscribed to these tags.I found similar question here but it more complex for me.
The code below is SQL I'm trying and it seems very slow for query times. I want SQL below is to write by using JOIN/LEFT JOIN/RIGHT JOIN I think it will be faster. But I have no idea.
SQL
select tag.*,
(select count(*) from taggedquestion where taggedquestion.tagid = tag.id) questionCount,
(select count(*) from tagsubscription where tagsubscription.tagid = tag.id) userCount
from tag order by tag.id asc
Tags page
Tag name question count user count
----------------------------------------------------------------
computer 12 5
science 10 3
travel 6 2
programing 18 3
... ... ...
... ... ...
This is your query:
select tag.*,
(select count(*) from taggedquestion where taggedquestion.tagid = tag.id and ids = '0'
) as questionCount,
(select count(*) from tagsubscription where tagsubscription.tagid = tag.id and ids = '0'
) as userCount
from tag
order by tag.id asc;
This is a reasonable query. Probably what you need for performance are the following indexes (if you don't already have them):
taggedquestion(tagid, ids)
tagsubscription(tagid, ids)
tag(id)
You probably already have the last one, because id is presumably a primary key on the tag table.
On your second posted query, you are missing SELECT and so getting the posted error. Your second query should look like below
SELECT tag.*,
( SELECT COUNT(*) FROM taggedquestion
LEFT JOIN tag ON tag.id = taggedquestion.tagid
and taggedquestion.ids = '0') AS postCount,
(SELECT COUNT(*) FROM tagsubscription
LEFT JOIN tag ON tag.id = tagsubscription.tagid
and tagsubscription.ids = '0') AS userCount
FROM tag
Try like this then using SUM with CASE condition. Since you haven't shown your tables structure or sample data; can't say for sure but you might want to group by the taggedquestion.ids like
SELECT tag.*,
( SELECT SUM(CASE WHEN taggedquestion.ids = '0' THEN 1 ELSE 0 END)
FROM taggedquestion
INNER JOIN tag
ON tag.id = taggedquestion.tagid
GROUP BY taggedquestion.ids) AS postCount,
(SELECT SUM(CASE WHEN tagsubscription.ids = '0' THEN 1 ELSE 0 END)
FROM tagsubscription
INNER JOIN tag
ON tag.id = tagsubscription.tagid
GROUP BY tagsubscription.ids) AS userCount
FROM tag

Custom site search by category

I want to create a simple search for a site that I'm working on. I have items in my db that all hold a specific category id and can optionally be linked to multiple tags.
I would like to take whatever search terms come in and query the category.name and tag.name fields to find the items that match those terms.
I'm looking for advice on how to create an efficient/quick query that does this AND orders the results by the items that match closest(most matches)
Here's a quick version of my relevant tables:
item
id | category | title | description
category
id | name | parentId
tag
id | name | uses
item_tag
itemId | tagId
I still didn't entirely understand what you want.
Well, here's a first version for us to discuss.
I suggest you to create a view as the following:
CREATE OR REPLACE VIEW `search_view` AS
SELECT
i.id AS `item_id`,
i.title AS `item_title`,
c.id AS `cat_id`,
c.name AS `cat_name`,
t.id AS `tag_id`,
t.name AS `tag_name`
FROM item AS i
LEFT OUTER JOIN item_tag AS it
ON (i.id = it.itemId)
LEFT OUTER JOIN tag AS t
ON (it.tagId = t.id)
LEFT OUTER JOIN category AS c
ON (i.category = c.id)
WHERE ((t.id IS NOT NULL) OR (c.id IS NOT NULL));
And then you query from the view using something near
SELECT
*,
(
IF(tag_name like ?, 2, 0)
+ IF(cat_name like ?, 4, 0)
+ IF(item_title like ?, 1, 0)
) AS `priority`
FROM search_view
GROUP BY item_id
ORDER BY SUM(priority);
No tests in the code above. Report any problems you have
[first edition] You use PHP, don't you?
Well, you can normalize your queries using PHP string functions; one way is to replace every occurrence of ',' by '|', remove extra spaces and perform the following query: [I'll give an example using #VAR (actually you'll replace it with your input string)]
SET #VAR = 'notebook|samsung';
SELECT
*,
(
IF (tag_name REGEXP CONCAT('.*(', #VAR, ').*'), 2, 0)
+ IF (cat_name REGEXP CONCAT('.*(', #VAR, ').*'), 4, 0)
+ IF (item_title REGEXP CONCAT('.*(', #VAR, ').*'), 1, 0)
) AS `priority`
FROM search_view
ORDER BY priority DESC;
This time I tested. Yes, you can use MySQL functions, something about REPLACE(REPLACE(#VAR,' ',''), ',', '|'). But I recommend you to do it in PHP (or java, python etc).
SELECT item.*
FROM items
LEFT JOIN categories
ON categories.name = '< input name >'
AND categories.id = items.category
LEFT JOIN item_tags
AND item_tags.itemId = items.id
LEFT JOIN tags
ON tags.name = '< input name >'
AND tags.id = item_tags.tagId
WHERE categories.id IS NOT NULL
OR tags.id IS NOT NULL
ORDER BY COUNT(items.id) DESC;
This may not be the fastest way. Basically you'll left join categories and tags to items while making sure the category and tag have the correct name. Filter out all items that don't match a category or an item_tag in the where clause.
Another alternative would be to create temporary tables. Create one for all categories with the correct name, and one with all tags with the correct name. You could then SELECT items WHERE items.id IN categories_table OR items.id IN tags_table