how to merge multiple rows with same id mysql - php

I'm new posting here but the community have been my best resource on my projects so far.
I'm a dumb/dummy Mysql "wanna be" and I'm in the middle of a project that is making me go mad.
I have a table from wordpress plugin buddypress that pairs meta_key and meta_values in order to create something akin to a taxonomy. My duty is to use these paired values to implement an advanced group search. Here is the original table:
--------------------------------------------
id | group_id | meta_key | meta_value
--------------------------------------------
1 | 1 | time-zone | Kwajalein
2 | 1 | playstyle | hardcore
3 | 1 | recruiting-status | Open
4 | 1 | ilvl | 115
5 | 1 | main-raid | Final Coil of Bahamut
6 | 1 | voicechat | fc.teamspeak3.com
etc....
Using a view I managed to create a more friendly searchable table for begginers :
gid| time-zone| playstyle | main-raid
--------------------------------------------
1 | | |
1 |Kwajalein | |
1 | | hardcore |
1 | | |
1 | | | Final Coil of Bahamut
1 | | |
And here is the view code:
SELECT distinct
group_id AS 'gid',
IF(meta_key='recruiting-status',meta_value,'') AS 'Recruitment',
IF(meta_key='server',meta_value,'') AS 'server',
IF(meta_key='time-zone',meta_value,'') AS 'tzone',
IF(meta_key='main-raid',meta_value,'') AS 'raid',
IF(meta_key='raid-days',meta_value,'') AS 'days',
IF(meta_key='playstyle',meta_value,'') AS 'playstyle',
IF(meta_key='raid-progression',meta_value,'') AS 'progression',
IF(meta_key='raid-time',meta_value,'') AS 'time',
IF(meta_key='tanker-spot',meta_value,'') AS 'tank',
IF(meta_key='healer-spot',meta_value,'') AS 'healer',
IF(meta_key='melee-dps-spot',meta_value,'') AS 'melee',
IF(meta_key='ranged-dps-spot',meta_value,'') AS 'ranged',
IF(meta_key='magic-dps-spot',meta_value,'') AS 'magic',
IF(meta_key='ilvl',meta_value,'') AS 'ilvl',
IF(meta_key='voicechat',meta_value,'') AS 'voice',
IF(meta_key='voicechatpass',meta_value,'') AS 'voicep',
FROM wpstatic_bp_groups_groupmeta
The point is, I need to merge that result (view) so all the group_id=1 or 2 or 3, etc stand in one single row, like this:
gid| time-zone| playstyle | main-raid
--------------------------------------------
1 |Kwajalein | hardcore | Final Coil of Bahamut
2 |SaoPaulo | regular | Second Coil of Bahamut
etc
Can anyone help me there?

Just surround your IFs in a MAX, or another aggregate function that will capture the non-empty strings (e.g., GROUP_CONCAT), and add a GROUP BY group_id add the end. For example,
SELECT
group_id AS gid,
MAX(IF(meta_key='recruiting-status',meta_value,'')) AS 'Recruitment',
MAX(IF(meta_key='server',meta_value,'')) AS 'server',
...
FROM wpstatic_bp_groups_groupmeta
GROUP BY group_id

Related

Select foreign key (group) where is the biggest match

I have three tables group_sentences, group_sentences_attributes and group_senteces_categories.
I have an attributes array which I am using in query with IN (after implode).
Then I have one category ID because they are stored recursively, so no need for an array.
I need to select one group number where is the biggest match for $attributesArray and of course category too.
Here is table group_sentences_attributes
+-----+-------+-----------+
| id | group | attribute |
+-----+-------+-----------+
| 1 | 1 | 3564 |
| 2 | 1 | 3687 |
| 3 | 1 | 3689 |
| 4 | 2 | 3687 |
| 5 | 2 | 3564 |
+-----+-------+-----------+
Here is group_sentences_category
+-----+-------+----------+
| id | group | category |
+-----+-------+----------+
| 1 | 1 | 1564 |
| 2 | 1 | 1221 |
| 3 | 1 | 1756 |
| 4 | 2 | 1358 |
| 5 | 2 | 1125 |
+-----+-------+----------+
Here is my query, but I am afraid that it won't do the job done.
SELECT group_categories.group
FROM group_categories, group_attributes
WHERE group_categories.category = '$category'
AND group_attributes.attribute IN ($attributesArray)
GROUP BY group_categories.group
ORDER BY count(group_attributes.attribute)
Any help would be appreciated, thanks.
First, the table in your query do not match the tables in the question. I am guessing they are simply missing the "sentence". Then, you have no join clause. Simple rule: Never use commas in the from clause.
group is a lousy name for a column, because it is a keyword in SQL. The following may be what you are looking for:
SELECT gc.groupid
FROM group_sentences_attributes sa JOIN
group_sentences_category sc
ON sa.groupid = sc.groupid
WHERE sc.category = '$category' AND
sa.attribute IN ($attributesArray)
GROUP BY sa.groupid
ORDER BY count(sa.attribute);
If you only want one row, then add LIMIT 1 to the end.

Dot product of one row against all rows in a many-column mySQL table

Background:
I have a ~400,000 row table which looks like the following:
+---------+--------+------+-------+------+-----+--------+
| ID | WORD | COL0 | COL1 | COL2 | ... | COL500 |
+---------|--------+------+-------+------+-----+--------+
| 0 | DOG | -0.73| 0.77 | 0.15 | | -0.55 |
| 1 | CAT | 0.41 | -0.57 | 0.61 | | 0.00 |
| 2 | HOUSE | 0.40 | 0.32 | -0.23| | 0.52 |
| ... | | | | | | |
| 400000 | LOVE | 0.51 | 0.59 | 0.01 | | -0.10 |
+---------+--------+------+-------+------+-----+--------+
Each col# represents a dimension of a 500 dim vector.
Problem:
Given a particular WORD value (they are unique), I want to find the 100 WORDs which are most similar to it based on the dot product (so an identical WORD vector will have a dot product of 1). So for the WORD 'CAR' I might get:
+--------+------+
| WORD | DOT |
+--------+------+
| CAR | 1 |
| TRUCK | 0.89 |
| SEDAN | 0.86 |
| VEHICLE| 0.81 |
| ... | ... |
| BIKE | 0.62 |
+--------+------+
So (to reiterate) I need to get the dot product of 'CAR' with every other word and sort it descending, and limit it to 100 results.
Potential solutions:
This SO question is very similar and was helpful, but I don't properly understand how to apply it ('garden' is being referred to as a table??).
Dot product in an SQL table with many columns
In the linked SO answer, 'garden' is a table: it's the table t, but aliased to garden, but limited to a single row (the one for the row with word 'GARDEN').
And for your particular question, you'd need to append 'ORDER BY DOT DESC LIMIT 100' to the end of the query.
Perhaps renaming it makes it clearer?
select allwords.*,
(allwords.col0 * word_of_interest.col0 +
allwords.col1 * word_of_interest.col1 + . . .
allwords.col500 * word_of_interest.col500
) as DOT
from allwords
cross join
(select allwords.*
from allwords
where `WORD` = '$THE_WORD_I_WANT_EG_CAR'
) as `word_of_interest`
order by `DOT` DESC LIMIT 100;
As the other answer says, I'd expect this to be fairly slow. If your COLn vector values are fairly static I'd consider pre-computing them and storing those results in a separate table that you'd query.

Optimising SQL Queries

I'm developing a content management system at the moment, and I wanted to hear your thoughts on the following:
I have one table, page. Let's assume it looks like this
ID | Title | Content
1 | Test | This is a test
As well as this, I have a page_option table (so I can store options relating to the page, but I don't want to have a finite list of options - modules could add their own options to a page if required.)
The page_option table could look like this:
page_id | option_key | option_value
1 | background | red
1 | module1_key | chicken
Now to retrieve a page object, I do the following using the Active Record class (this was pseudo coded for this question):
function get_by_id($page_id) {
$this->db->where('id', $page_id);
$page_object = $this->db->get('page');
if($page_object->num_rows() > 0) {
$page = $page_object->row();
$this->db->where('page_id', $page_id);
$options_object = $this->db->get('option');
if($options_object->num_rows() > 0) {
$page->options = $options_object->result();
}
return $page;
}
return $page_object->row();
}
What I want to know, is there a way to do this in one query, so that the option keys become virtual columns in my select, so I'd get:
ID | Title | Content | background | module1_key
1 | Test | This is a test | red | chicken
In my results, rather than doing a seperate query for every row. What if there were 10,000? Etc.
Many thanks in advance!
Using the EAV (Entity-Attribute-Value) model you will always have to cope with these kind of issues. They're also not ver efficient due to the complexity of the queries (pivoting is required in most of them).
SELECT page_id,
MAX(CASE WHEN option_key = 'background' THEN option_value END) background,
MAX(CASE WHEN option_key = 'module1_key' THEN option_value END) module1_key,
MAX(CASE WHEN option_key = 'module2_key' THEN option_value END) module2_key
FROM page_option
GROUP BY page_id
For example, given this table:
| PAGE_ID | OPTION_KEY | OPTION_VALUE |
|---------|-------------|--------------|
| 1 | background | red |
| 1 | module1_key | chicken |
| 2 | module1_key | duck |
| 3 | module1_key | cow |
| 4 | background | blue |
| 4 | module2_key | alien |
| 4 | module1_key | chicken |
You will the following output:
| PAGE_ID | BACKGROUND | MODULE1_KEY | MODULE2_KEY |
|---------|------------|-------------|-------------|
| 1 | red | chicken | (null) |
| 2 | (null) | duck | (null) |
| 3 | (null) | cow | (null) |
| 4 | blue | chicken | alien |
Fiddle here.
Then just join with the page table and that's it :) I've omitted that part in order to focus the query in the grouping itself.
If you can add virtual fields with the activerecord class you can do something similar:
$this->db->add_field("(select group_concat(concat(option_key,':',option_value) SEPARATOR ' ') from page_option where page_id=$page_id group by page_id)");
It wont be optimal...
If option_key is uniqe per page_id (you don't have two or more background with page_id==1) you can do:
SELECT page.page_id, page.title, page.content,
GROUP_CONCAT(option_key SEPARATOR '#') AS option_keys,
GROUP_CONCAT(option_value SEPARATOR '#') as option_values,
FROM page
LEFT JOIN page_option ON page_option.page_id=page.page_id
WHERE page.page_id=USER_SPECIFIED_ID
You can execute this SQL-query and put its result into $result. After you should do every item of $result:
$result[$i]["options"] = array_combine(
explode("#",$result[$i]["option_keys"]),
explode("#",$result[$i]["option_values"])
);
You can do it with a foreach or you can use array_walk too.
After these you've an associative array with options in $result[$i]["options"]:
{
"background" => "red",
"module_key1"=> "chicken"
}
I hope it's what do you want.

Updating MySQL row with values from another table using PDOs

I have a table like the one below that currently has no values for rating, lib_id or votes.
library
id | title | year | rating | votes | lib_id |
---------------------------------------------
1 | book1 | 1999 | | | |
2 | book2 | 2010 | | | |
3 | book3 | 2009 | | | |
4 | book4 | 2007 | | | |
5 | book5 | 1987 | | | |
I then have the classifications table which looks like this.
classifications
id | title | year | rating | votes | lib_id |
---------------------------------------------
108 | book154 | 1929 | | | |
322 | book23 | 2011 | | | |
311 | book3 | 2009 | 9.3 | 4056 | 10876 |
642 | book444 | 2001 | | | |
533 | book567 | 1981 | | | |
It can happen that entries in the library table may not appear in the classifications table and vice-versa. There can also be the possibility that the title of the book is not unique. So what I want to do is go through each row in the library table, take the title and year columns, go to the classifications table and find the row that has these two values, retrieve the corresponding rating, votes and lib_id columns and update the entry in the library table.
I also want to use PDOs. Below is a non-working example of what i'm trying to achieve.
$update_vals_STH =
$DBH->prepare(
"UPDATE library SET lib_id=?, rating=?, votes=?
FROM (SELECT lib_id, rating, votes)
FROM classifications WHERE title=? AND year=?";
Any help would be appreciated. I'm quite new to MySQL and have been struggling with this one for a while.
You can join tables on update statement too.
UPDATE library a
INNER JOIN classifications b
ON a.title = b.title AND
a.year = b.year
SET a.rating = b.rating,
a.votes = b.votes,
a.lib_id = b.lib_id
// WHERE clause // if you want to have extra condition.
SQLFiddle Demo
UPDATE
For better performance, you need to add indexes on the following field.
ALTER TABLE library ADD INDEX (title, year);
ALTER TABLE classifications ADD INDEX (title, year);

Delimited foreign ID's in a mysql field

Preface: Yes i realize this is bad design, but i can't change this.
Question
I have a customers table, and within that a field 'products'. Here is an example of what is in a sample customers products field:
36;40;362
Each of those numbers reference a record from the products table. I'm trying to do a
(SELECT group_concat(productName) from products where productID=???)
but am having trouble with the delimiters. I know how to remove the semi colons, and have tried 'where INSTR' or IN but am having no luck.
Is the best approach to return the whole field to PHP and then explode / parse there?
You can use FIND_IN_SET function in MySQL.
You just need to replace semicolons with a comma and the use it in your query:
SELECT group_concat(productName)
FROM products
WHERE FIND_IN_SET(productID, ???) > 0
Just remember that ??? should be comma-separated!
Like you said, this isn't the way to do it. But since it's an imperfect world:
Assuming a database structure like so:
+-PRODUCTS---------+ +-CUSTOMERS---------+------------+
| ID | productName | | ID | customerName | productIDs |
+----+-------------+ +----+--------------+------------+
| 1 | Foo | | 1 | Alice | 1;2 |
+----+-------------+ +----+--------------+------------+
| 2 | Bar | | 2 | Bob | 2;3 |
+----+-------------+ +----+--------------+------------+
| 3 | Baz | | 3 | Charlie | |
+----+-------------+ +----+--------------+------------+
Then a query like this:
SELECT customers.*,
GROUP_CONCAT(products.id) AS ids,
GROUP_CONCAT(productName) AS names
FROM customers
LEFT JOIN products
ON FIND_IN_SET(products.id, REPLACE(productIDs, ";", ","))
GROUP BY customers.id
Would return:
+-RESULT------------+------------+-----+---------+
| ID | customerName | productIDs | ids | names |
+----+--------------+------------+-----+---------+
| 1 | Alice | 1;2 | 1,2 | Foo,Bar |
+----+--------------+------------+-----+---------+
| 2 | Bob | 2;3 | 1,2 | Bar,Baz |
+----+--------------+------------+-----+---------+
| 3 | Charlie | | 1,2 | NULL |
+----+--------------+------------+-----+---------+
FIND_IN_SET( search_value, comma_separated_list ) searches for the value in the given comma separated string. So, you need to replace the semicolons with commas, which is obviously what REPLACE() does. The return value of this function is the position where it found the first match, so for example:
SELECT FIND_IN_SET(3, '1,3,5') = 2
SELECT FIND_IN_SET(5, '1,3,5') = 3
SELECT FIND_IN_SET(7, '1,3,5') = NULL

Categories