Wordpress is a good example of a web application that uses a table for user info, and then a meta lookup table for user data. The only problem is that the only way I know of to get a complete list of meta information for a list of users is to build the sql statement "manually" - either hard coded or with the help of PHP.
The user table looks something like this:
wp_users table
ID|user_login|user_email|user_pass|date_registered
==================================================
1| me |me#me1.com|f239j283r| 2011-01-01
wp_usermeta table
umeta_id|user_id|meta_key|meta_value
====================================
1 | 1 | phone | 123-4567
1 | 1 | fname | john
1 | 1 | lname | doe
I know I can do something like this (manually or with php) to achieve the result of what I want:
select *
from wp_users
left join wp_usermeta as phone on (ID = user_id) AND (meta_key = phone)
left join wp_usermeta as fname on (ID = user_id) AND (meta_key = fname)
left join wp_usermeta as lname on (ID = user_id) AND (meta_key = lname)
that yields something like this:
ID|user_login|user_email|user_pass|date_registered|phone |fname|lname
=================================================================+++===
1| me |me#me1.com|f239j283r| 2011-01-01 |123-4567|john |doe
I know mySql also has the GROUP_CONCAT thing, which is why I feel like there is a better way. That would look something like this:
select *, group_concat(meta_value) as all_meta
from wp_users
left join wp_usermeta on ID = user_id
group by wp_users.ID
So is there a way to get the result similar to that from the first sql statement with a more dynamic sql statement like the second one?
Edit
Doug has proposed an interesting solution, possibly using information_schema. I was having trouble getting that to work so I've posted a dump of the two tables for anyone who wants to test their SQL :) http://pastebin.com/w0jkxnws
Is this what you're looking for? It's still 3 statements, but, contrary to my previous statement to the contrary, there shouldn't be much prep cost.
set group_concat_max_len = 2048;
SELECT CONCAT('SELECT u.id, u.user_login, ', GROUP_CONCAT(concat('
(SELECT meta_value FROM wp_usermeta WHERE user_id = u.id AND meta_key = "', um.meta_key, '") `', um.meta_key, '`') SEPARATOR ", "), '
FROM wp_users u ') FROM (SELECT DISTINCT meta_key FROM wp_usermeta) um INTO #sql;
PREPARE stmt1 FROM #sql;
EXECUTE stmt1;
Usually this is done by running 2 queries against the database – first to fetch user record, second for properties.
Try this
select *
from wp_users
left join wp_usermeta as fname on (ID = user_id)
where meta_key in ('fname','lname','phone')
group by ID, meta_key;
You could try something like this:
SELECT
u.*,
MIN(CASE m.meta_key WHEN 'phone' THEN m.meta_value END) AS phone,
MIN(CASE m.meta_key WHEN 'fname' THEN m.meta_value END) AS fname,
MIN(CASE m.meta_key WHEN 'lname' THEN m.meta_value END) AS lname
FROM wp_users u
LEFT JOIN wp_usermeta m ON u.ID = m.user_id
AND m.meta_key IN ('phone', 'fname', 'lname')
GROUP BY u.ID
Related
I have a messaging app that I want to display messages like facebook, it should get the last send message by either the sender or recipient, my table layout is like this:
messages_tbl
__________________________________________________________________________
|id | user1Fk| user2Fk |subject | user1Delete | user2Delete | dateCreated |
and my user_tbl
_______________________________________
| id | first_name | last_name | image |
my Query
SELECT `id` , `user1Fk` as sender_id,
(SELECT concat(first_name,\" \",last_name)
FROM user_tbl
WHERE user_tbl.id = sender_id
) as senderName,
`user2Fk` as recipient_id ,
(SELECT concat(first_name,\" \",last_name)
FROM user_tbl
WHERE user_tbl.id = recipient_id
) as recipientName,
(SELECT image
FROM user_tbl
WHERE user_tbl.id = sender_id
) as senderImage,
(SELECT image
FROM user_tbl
WHERE user_tbl.id = recipient_id
) as recipientImage,
`subject`, `message`, `user1Delete`, `user2Delete`,
`dateCreated`
FROM `message_tbl`as m1
WHERE dateCreated = (SELECT MAX(m2.dateCreated)
from message_tbl as m2
WHERE (m1.user1Fk = m2.user1Fk
AND m1.user2Fk = m2.user2Fk
OR m1.user1Fk = m2.user2Fk
AND m1.user2Fk = m2.user1Fk
)
) AND ? IN (m1.user1Fk, m1.user2Fk)
ORDER BY dateCreated DESC
This query is working for the most part but its lacking, I want it to check, if the given id by the ? matches user1Fk it should then check if user1Delete is a 0 or 1, if its a 1 then do not display the message user 1 deleted it, the same with user 2 but I cannot think of the logic, can anyone help me?
I'm assuming that the question mark is there because this is a prepared query and the question mark should be replaced with a user id.
Your query is a little bit over complicated because there are a lot of subselects. I tried to improve it a bit for better performance and to be able to extend it easier. With SQL you can join tables instead of using subqueries, this is better for performance, and it makes it easier to add conditions to the joined tables.
In the example below I added a condition for both the user1 and user2 delete fields to be 0. I set the limit to 1, which means only the first row will be returned. Together with the descending order of the date, this means that only the newest message will be returned. I also changed the question mark to :user_id because in this example it is used twice and this way you'll only have to add it once.
SELECT
m.id,
u1.id AS sender_id,
CONCAT(u1.first_name, ' ', u1.last_name) AS senderName,
u2.id AS recipient_id,
CONCAT(u2.first_name, ' ', u2.last_name) AS recipientName,
u1.image AS senderImage,
u2.image AS recipientImage,
m.subject,
m.message,
m.user1Delete,
m.user2Delete,
m.dateCreated
FROM messages_tbl m
INNER JOIN user_tbl u1 ON m.user1Fk = u1.id
INNER JOIN user_tbl u2 ON m.user2Fk = u2.id
WHERE (m.user1Delete = 0 AND u1.id = :user_id)
OR (m.user2Delete = 0 AND u2.id = :user_id)
ORDER BY m.dateCreated DESC
LIMIT 1
I haven't been able to test it but I hope this is what you were looking for.
I currently have the following EAV table:
id
field_name
field_value
And a standard table:
id
first_name
last_name
I am joining the standard table onto the EAV table for each value that matches the ID, so my query looks something like this:
SELECT id, first_name, last_name, fieldname1, fieldname2
FROM standard_table
LEFT JOIN EAV AS fieldname1 ON
(fieldname1.id = standard_table.id AND fieldname1.field_name = 'fieldname1')
LEFT JOIN EAV AS fieldname2 ON
(fieldname2.id = standard_table.id AND fieldname2.field_name = 'fieldname2');
This has been working fine, up until today where I now have 62 custom fields in my EAV table, this means that my query is joining onto 62 tables and so hitting the MySQL table join limit and failing.
The whole query seems like a bad way of doing it, how can I rewrite this so it is quicker and doesn't require 62 table joins.
You can also use aggregation for EAV. The query looks like:
SELECT st.id, st.first_name, st.last_name,
MAX(CASE WHEN EAV.field_name = 'fieldname1' THEN fieldname1 END),
MAX(CASE WHEN EAV.field_name = 'fieldname2' THEN fieldname2 END)
FROM standard_table st JOIN
EAV
ON EAV.id = st.id
GROUP BY st.id, st.first_name, st.last_name;
As you get more and more columns, this can perform better than dozens of joins.
The other provided answer was the inspiration for this however I below is the query I actually used:
SELECT st.id, st.first_name, st.last_name,
(CASE WHEN `EAV`.`field_name` = 'fieldname1' THEN `EAV`.`field_value` END) AS 'fieldname1',
(CASE WHEN `EAV`.`field_name` = 'fieldname2' THEN `EAV`.`field_value` END) AS 'fieldname2',
FROM standard_table st JOIN
EAV
ON EAV.id = st.id
GROUP BY st.id;
I am creating a blog post system in which I am using following table structure :-
blog_category
category_id, category_name, enabled, created_date
blog_post
post_id,title,article, author_id,date_published
post_category
category_id, post_id
blog_tag
tag_id, tag_name
post_tag
id,tag_id,post_id
post_related
id, post_id, post_related_id
blog_comment
comment_id, post_id, is_reply_to_id,comment,user_id
My questions ARE:
How to get this type of output in a single query
title,author_name,article,total_comments,post_tags
How to insert records in post_related table to show the related post when select a particular post.
I trying like this but it show unknown column bp.post_id
CREATE PROCEDURE blog_get_posts_in_category(
IN inCategoryId INT, IN inShortPostDescriptionLength INT,
IN inPostsPerPage INT, IN inStartItem INT)
BEGIN
PREPARE statement FROM
"SELECT bp.post_id, bp.title,(select count(*) from blog_comment where post_id=bp.post_id) as total_comments,
IF(LENGTH(bp.article) <= ?,
bp.article,
CONCAT(LEFT(bp.article, ?),
'...')) AS article,
DATE_FORMAT(bp.date_published,'%d %M %Y at %h:%i:%s %p') as date_published, bp.banner_image,ba.display_name
FROM blog_post bp
INNER JOIN blog_post_to_category bpc
ON bpc.post_id = bp.post_id
INNER JOIN blog_author ba
ON ba.id = bp.author_id
WHERE bpc.category_id = ? and enabled=1
ORDER BY bp.post_id DESC
LIMIT ?, ?";
SET #p1 = inShortPostDescriptionLength;
SET #p2 = inShortPostDescriptionLength;
SET #p3 = inCategoryId;
SET #p4 = inStartItem;
SET #p5 = inPostsPerPage;
EXECUTE statement USING #p1, #p2, #p3, #p4, #p5;
END$$
Q1:
This is not really clear from the question, so lets suppose that
- blog_author table looks like
author_id, author_name, ...
- total_comments is the number of all the comments belonging to a blog post.
- post_tags could be one string containing all the post_tags related to a blog post (e.g. as a csv).
Then, a query could be:
SELECT
bp.post_id as pid, bp.title, a.author_name, bp.article, bc.total_comments, pt.post_tags
FROM blog_post bp
LEFT JOIN blog_author a ON bp.author_id=a.id
LEFT JOIN (
SELECT post_id, COUNT(*) as total_comments
FROM blog_comment
GROUP BY post_id) bc
ON bc.post_id=bp.post_id
LEFT JOIN (
SELECT post_id, GROUP_CONCAT(DISTINCT tag_id SEPARATOR ',') AS post_tags
FROM post_tag
GROUP BY post_id) pt
ON pt.post_id=bp.post_id;
Q2:
Here, the query seems to be OK. I even tried it myself and I got no error message about the bp.post_id. Just one idea that the subquery with the total_comments could be avoided using the join (with the blog_comment table) that I show in my previous query.
But this procedure/query does something different than the question supposed to be about. (It does not even have any reference to the blog_related table.)
Some background:
I'm developing kind of social network.
There is DB with next tables:
users(user_id, user_name, password, etc.)
posts(post_id, user_id, post_text, post_data, etc.)
followings(relationship_id, follower_id, following_id, added_date_time)
likes(like_id, user_id, post_id, added_date_time)
comments(comment_id, user_id, post_id, comment_text, added_date_time)
The question is:
How can I implement query which would fetch information about what my followings(people I follow) have did recently (e.g. someone liked/commented something) and order this array of info by date_time?
Can I make it using one query? Or I'll have to make multiple queries and handle all this stuff in PHP by myself?
What is the best approach?
You can normalize the output in a select union like this one
SELECT t.type, t.user_id, u.user_name, t.type_id, t.post_id, t.text, t.added_date_time
FROM (
SELECT 'posts' as type, p.user_id, p.post_id as type_id, p.post_id, post_text as text, p.added_date_time
FROM posts p
JOIN followings f ON (f.following_id = p.user_id)
WHERE f.follower_id = #user
UNION
SELECT 'comments' as type, c.user_id, c.comment_id as type_id, c.post_id, comment_text as text, c.added_date_time
FROM comments c
JOIN followings f ON (f.following.id = c.user_id)
WHERE f.follower_id = #user
UNION
SELECT 'likes' as type, l.user_id, l.like_id as type_id, l.post_id, post_text as text, l.added_date_time
FROM likes l
JOIN followings f ON (f.following.id = c.user_id)
JOIN posts p ON (l.post_id = p.post_id)
WHERE f.follower_id = #user
) as t
JOIN users u ON (t.user_id = u.user_id)
ORDER BY added_date_time DESC;
So you can get all the data in only one query.
I hope it works fine for you.
You have to seperate at least the queries for likes and comments, since the result set is different.
Otherwise your query would look e.g. like:
SELECT comment_id,comment_text,added_date_time FROM comments WHERE user_id IN (SELECT following_id FROM followings WHERE follower_id={{USER_ID_FROM_USER}}) ORDER BY added_date_time
This will get you the comments from people which are followed by {{USER_ID_FROM_USER}} ordered by date.
Suppose this:
users
id | name | address
partners
id | name | company | address
Even though the tables are distinct sometimes happens having to associate the users or partners id to the same function ..
For example, the access table
acl
uri | uid | group | operations
Here "uid" can be both user and partner.
How can I read the data of 2 tables with the same alias?
something like:
$selectQuery = <<<QUERY
SELECT A. *,
U.name P.name AS username,
G.name AS groupname
FROM [acl] A
LEFT JOIN [users] U ON A.uid = U.id
LEFT JOIN [partner] P ON A.uid = P.id
LEFT JOIN [groups] G ON A.gid = G.id
WHERE A.id =: id
LIMIT 0.1
QUERY;
You probably want to use COALESCE or IFNULL
IFNULL(U.name, P.name) AS username,
COALESCE(U.name, P.name) AS username,
Both of those will do the exact same thing in this situation. If U.name is not NULL then username will be U.name, otherwise it'll be P.name.
USE UNION ALL with the condition......it should help hopefully