MySQL GROUP_CONCAT and JOIN - mysql

Media table:
CREATE TABLE $media_table (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(300) DEFAULT NULL,
`options` longtext DEFAULT NULL,
PRIMARY KEY (`id`)
}
Table example:
id title options
--------------------------
1 video ...
2 video ...
3 audio ...
Category table:
CREATE TABLE $media_taxonomy_table (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`type` varchar(15) DEFAULT NULL,
`title` varchar(300) DEFAULT NULL,
`media_id` int(11) unsigned DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `media_id` (`media_id`)
}
type can be 'category' or 'tag' (I guess this could be enum)
Table example:
id type title media_id
---------------------------------------
1 category rock 1
2 category trance 1
3 category trance 2
4 category rock 3
5 tag silent 1
5 tag loud 1
6 tag foo 2
I am trying to GROUP_CONCAT on both category and tag from $media_taxonomy_table.
This query will return GROUP_CONCAT with only category
SELECT mt.id, mt.title, mt.options, GROUP_CONCAT(mtt.title ORDER BY mtt.title ASC SEPARATOR ', ') as category
FROM $media_table as mt
LEFT JOIN $media_taxonomy_table as mtt
ON mt.id = mtt.media_id AND mtt.type='category'
WHERE playlist_id = %d
GROUP BY mt.id
Results, received:
id title options category
----------------------------------------
1 video ... rock, trance
2 video ... trance
3 audio ... rock
expected (I need tag as well):
id title options category tag
--------------------------------------------------------------
1 video ... rock, trance silent, load
2 video ... trance foo
3 audio ... rock

You can do conditional aggregation:
SELECT
mt.id,
mt.title,
mt.options,
GROUP_CONCAT(CASE WHEN mtt.type = 'category' THEN mtt.title END ORDER BY mtt.title SEPARATOR ', ') as categories,
GROUP_CONCAT(CASE WHEN mtt.type = 'tag' THEN mtt.title END ORDER BY mtt.title SEPARATOR ', ') as tags
FROM $media_table as mt
LEFT JOIN $media_taxonomy_table as mtt ON mt.id = mtt.media_id
WHERE mt.playlist_id = %d
GROUP BY mt.id, mt.title, mt.options

Seems you need another group_concat so you need another join
SELECT mt.id
, mt.title
, mt.options
, GROUP_CONCAT(mtt.title ORDER BY mtt.title ASC SEPARATOR ', ') as category
, GROUP_CONCAT(mtt2.title ORDER BY mtt.title ASC SEPARATOR ', ') as tag
FROM $media_table as mt
LEFT JOIN $media_taxonomy_table as mtt
ON mt.id = mtt.media_id AND mtt.type='category'
LEFT JOIN $media_taxonomy_table as mtt2
ON mt.id = mtt.media_id AND mtt.type='tag'
WHERE playlist_id = %d
GROUP BY mt.id

Related

Why isn't it showing all descendant categories in breadcrumbs?

When searching by product category, I need to display all of its parent categories.
When there are multiple levels, it only displays two results.
CREATE TABLE `category` (
`id` int NOT NULL,
`parent_category_id` int,
`name` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL
) ENGINE=InnoDB;
INSERT INTO `category` (`id`, `parent_category_id`, `name`) VALUES
(1, NULL, 'Male'),
(2, 1, 'T-shirts'),
(3, 1, 'Shoes'),
(4, 2, 'Tank top'),
(5, 2, 'Basic shirts');
SELECT CONCAT(t1.name, ' > ', t2.name) as breadcrumb
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent_category_id = t1.id
WHERE t2.id = 4; #( 4 or 5 )
Result: T-shirts > Tank top
Expected outcome: Male > T-shirts > Tank top
Does not display the "Male" category
Well, you only join one level. If you want to join arbitrary levels, you can use a recursive CTE.
WITH RECURSIVE
breadcrumbs
AS
(
SELECT 1 AS ordinality,
c.name,
c.subcategory_id
FROM category AS c
WHERE c.id = 4
UNION ALL
SELECT bc.ordinality + 1 AS ordinality,
c.name,
c.subcategory_id
FROM breadcrumbs AS bc
INNER JOIN category AS c
ON c.id = bc.subcategory_id
)
SELECT group_concat(bc.name
ORDER BY bc.ordinality DESC
SEPARATOR ' > ') AS breadcrumb
FROM breadcrumbs AS bc;
db<>fiddle

SQL query to select all rows with max column value

CREATE TABLE `user_activity` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`type` enum('request','response') DEFAULT NULL,
`data` longtext NOT NULL,
`created_at` datetime DEFAULT NULL,
`source` varchar(255) DEFAULT NULL,
`task_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
);
I have this data:-
Now I need to select all rows for user_id=527 where created_at value is the maximum. So I need the last 3 rows in this image.
I wrote this query:-
SELECT *
FROM user_activity
WHERE user_id = 527
AND source = 'E1'
AND task_name IN ( 'GetReportTask', 'StopMonitoringUserTask' )
AND created_at = (SELECT Max(created_at)
FROM user_activity
WHERE user_id = 527
AND source = 'E1'
AND task_name IN ( 'GetReportTask',
'StopMonitoringUserTask' ));
This is very inefficient because I am running the exact same query again as an inner query except that it disregards created_at. What's the right way to do this?
I would use a correlated subquery:
SELECT ua.*
FROM user_activity ua
WHERE ua.user_id = 527 AND source = 'E1' AND
ua.task_name IN ('GetReportTask', 'StopMonitoringUserTask' ) AND
ua.created_at = (SELECT MAX(ua2.created_at)
FROM user_activity ua2
WHERE ua2.user_id = ua.user_id AND
ua2.source = ua.source AND
ua2.task_name IN ( 'GetReportTask', 'StopMonitoringUserTask' )
);
Although this might seem inefficient, you can create an index on user_activity(user_id, source, task_name, created_at). With this index, the query should have decent performance.
Order by created_at desc and limit your query to return 1 row.
SELECT *
FROM user_activity
WHERE user_id = 527
AND source = 'E1'
AND task_name IN ( 'GetReportTask', 'StopMonitoringUserTask' )
ORDER BY created_at DESC
LIMIT 1;
I used EverSQL and applied my own changes to come up with this single-select query that uses self-join:-
SELECT *
FROM user_activity AS ua1
LEFT JOIN user_activity AS ua2
ON ua2.user_id = ua1.user_id
AND ua2.source = ua1.source
AND ua2.task_name IN ( 'GetReportTask', 'StopMonitoringUserTask' )
AND ua1.created_at < ua2.created_at
WHERE ua1.user_id = 527
AND ua1.source = 'E1'
AND ua1.task_name IN ( 'GetReportTask', 'StopMonitoringUserTask' )
AND ua2.created_at IS NULL;
However, I noticed that the response times of both queries were similar. I tried to use Explain to identify any performance differences; and from what I understood from its output, there are no noticeable differences because proper indexing is in place. So for readability and maintainability, I'll just use the nested query.

fetch datas from two tables and differentiate between them

I have two tables and want displays rows from the two one in the same page ordered by date created.
Here my query:
SELECT R.*, R.id as id_return
FROM return R
UNION
ALL
SELECT A.*, A.id as id_buy
FROM buy A
WHERE
R.id_buyer = '$user' AND R.id_buyer = A.id_buyer AND (R.stats='1' OR R.stats='3') OR A.stats='4'
ORDER
BY R.date, A.date DESC LIMIT $from , 20
With this query i get this error message:
Warning: mysqli_fetch_array() expects parameter 1 to be mysqli_result, boolean given in ...
And here how i think i can differentiate between the results: (Knowing if the result is from the table RETURN or from the table BUY)
if(isset($hist_rows["id_return"])) {
// show RETURN rows
} else {
// show BUY rows
}
Please what is wrong with the query, and if the method to differentiate between tables are correct ?
EDIT
Here my tables sample:
CREATE TABLE IF NOT EXISTS `return` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`id_buyer` INT(12) NOT NULL,
`id_seller` INT(12) NOT NULL,
`message` TEXT NOT NULL,
`stats` INT(1) NOT NULL,
`date` varchar(30) NOT NULL,
`update` varchar(30)
PRIMARY KEY (`id`)
)
CREATE TABLE IF NOT EXISTS `buy` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`id_buyer` INT(12) NOT NULL,
`product` INT(12) NOT NULL,
`title` VARCHAR(250) NOT NULL,
`stats` INT(1) NOT NULL,
`date` varchar(30) NOT NULL
PRIMARY KEY (`id`)
)
Be sure the two table return and buy have the same number (and type sequence) of colummns .. if not the query fails
try select only the column you need from both the table and be sure that these are in correspondenting number and type
SELECT R.col1, R.col2, R.id as id_return
FROM return R
UNION ALL
SELECT A.col1, A.col2, A.id as id_buy
FROM buy A
WHERE
........
Looking to your code you should select the same number and type of column form boith the table eg de sample below:
(where i have added the different column and selecting null from the table where are not present)
I have aslore referred the proper where condition to each table ..
SELECT
R.'from return' as `source_table`
, R.`id`
, R.`id_buyer`
, null as product
, null as title
, R.`id_seller` as id_seller
, R-`message`
, R.`stats`
, R.`date`
, R.`update`
FROM return R
WHERE R.id_buyer = '$user'
AND (R.stats='1' OR R.stats='3')
UNION ALL
SELECT
A.'from buy'
, A.`id`
, A.`id_buyer`
, A.`product`
, A.`title`
, null
, null
, A.`stats`
, A.`date`
, null
FROM buy A
WHERE
A.id_buyer = '$user'
AND A.stats='4'
ORDER BY `source table`, date DESC LIMIT $from , 20
for retrive te value of the first column you should use in your case
echo $hist_rows["source_table"];
Otherwise i the two table are in some way related you should look at a join (left join) for link the two table and select the the repated column
(but this is another question)
But if you need left join you can try
SELECT
R.`id`
, R.`id_buyer`
, R.`id_seller` as id_seller
, R-`message`
, R.`stats`
, R.`date`
, R.`update`
, A.`id`
, A.`id_buyer`
, A.`product`
, A.`title`
, null
, null
, A.`stats`
, A.`date`
FROM return R
LEFT JOIN buy A ON R.id_buyer = A.id_buyer
AND R.id_buyer = '$user'
AND (R.stats='1' OR R.stats='3')
AND A.stats='4'
ORDER BY R.date DESC LIMIT $from , 20
When you use union all, the queries need to have exactly the same columns in the same order. If the types are not quite the same, then they are converted to the same type.
So, you don't want union all. I'm guessing you want a join. Something like this:
SELECT r.co1, r.col2, . . ., r.id as id_return,
b.col1, b.col2, . . ., b.id as id_buy
FROM return r JOIN
buy b
ON r.id_buyer = b.id_buyer
WHERE r.id_buyer = '$user' and
(r.stats in (1, 3) OR A.stats = 4)
ORDER BY R.date, A.date DESC
LIMIT $from, 20;
This query is only a guess as to what you might want.
Since you're using a union, select a string that you set identifying each query:
SELECT 'R', R.*, R.id as id_return
FROM return R
UNION
ALL
SELECT 'A', A.*, A.id as id_buy
This way your string 'R' or 'A' is the first column, showing you where it came from. We can't really know why it's failing without the full query, but I'd guess your $from might be empty?
As for your
Warning: mysqli_fetch_array() expects parameter 1 to be mysqli_result, boolean given in ...
Run the query directly first to get the sql sorted out before putting it into your PHP script. The boolean false indicates the query failed.

optimize mysql query: medal standings

I have 2 tables:
olympic_medalists with columns gold_country, silver_country, bronze_country
flags with country column
I want to list the olympic medal table accordingly. I have this query, it works, but it seems to kill mysql. Hope someone can help me with an optimized query.
SELECT DISTINCT country AS sc,
IFNULL(
(SELECT COUNT(silver_country)
FROM olympic_medalists
WHERE silver_country = sc AND silver_country != ''
GROUP BY silver_country),0) AS silver_medals,
IFNULL(
(SELECT COUNT(gold_country)
FROM olympic_medalists
WHERE gold_country = sc AND gold_country != ''
GROUP BY gold_country),0) AS gold_medals,
IFNULL(
(SELECT COUNT(bronze_country)
FROM olympic_medalists
WHERE bronze_country = sc AND bronze_country != ''
GROUP BY bronze_country),0) AS bronze_medals
FROM olympic_medalists, flags
GROUP BY country, gold_medals, silver_country, bronze_medals HAVING (
silver_medals >= 1 || gold_medals >= 1 || bronze_medals >= 1)
ORDER BY gold_medals DESC, silver_medals DESC, bronze_medals DESC,
SUM(gold_medals+silver_medals+bronze_medals)
result will be like:
country | g | s | b | tot
---------------------------------
country1 | 9 | 5 | 2 | 16
country2 | 5 | 5 | 5 | 15
and so on
Thanks!
olympic medalists:
`id` int(8) NOT NULL auto_increment,
`gold_country` varchar(64) collate utf8_unicode_ci default NULL,
`silver_country` varchar(64) collate utf8_unicode_ci default NULL,
`bronze_country` varchar(64) collate utf8_unicode_ci default NULL, PRIMARY KEY (`id`)
flags
`id` int(11) NOT NULL auto_increment,
`country` varchar(128) default NULL,
PRIMARY KEY (`id`)
This will be much more efficient than your current solution of executing three different SELECT subqueries for each row in a cross-joined relation (and you wonder why it stalls out!):
SELECT a.country,
COALESCE(b.cnt,0) AS g,
COALESCE(c.cnt,0) AS s,
COALESCE(d.cnt,0) AS b,
COALESCE(b.cnt,0) +
COALESCE(c.cnt,0) +
COALESCE(d.cnt,0) AS tot
FROM flags a
LEFT JOIN (
SELECT gold_country, COUNT(*) AS cnt
FROM olympic_medalists
GROUP BY gold_country
) b ON a.country = b.gold_country
LEFT JOIN (
SELECT silver_country, COUNT(*) AS cnt
FROM olympic_medalists
GROUP BY silver_country
) c ON a.country = c.silver_country
LEFT JOIN (
SELECT bronze_country, COUNT(*) AS cnt
FROM olympic_medalists
GROUP BY bronze_country
) d ON a.country = d.bronze_country
What would be even faster is instead of storing the actual textual country name in each of the gold, silver, and bronze columns, just store the integer-based country id. Comparisons on integers are always going to be faster than comparisons on strings.
Moreover, once you replace each country name in the olympic_medalists table with the corresponding id's, you'll want to create an index on each column (gold, silver, and bronze).
Updating the textual names to be their corresponding id's instead is a simple task and could be done with a single UPDATE statement in conjunction with some ALTER TABLE commands.
try this:
SELECT F.COUNTRY,IFNULL(B.G,0) AS G,IFNULL(B.S,0) AS S,
IFNULL(B.B,0) AS B,IFNULL(B.G+B.S+B.B,0) AS TOTAL
FROM FLAGS F LEFT OUTER JOIN
(SELECT A.COUNTRY,
SUM(CASE WHEN MEDAL ='G' THEN 1 ELSE 0 END) AS G,
SUM(CASE WHEN MEDAL ='S' THEN 1 ELSE 0 END) AS S,
SUM(CASE WHEN MEDAL ='B' THEN 1 ELSE 0 END) AS B
FROM
(SELECT GOLD_COUNTRY AS COUNTRY,'G' AS MEDAL
FROM OLYMPIC_MEDALISTS WHERE GOLD_COUNTRY IS NOT NULL
UNION ALL
SELECT SILVER_COUNTRY AS COUNTRY,'S' AS MEDAL
FROM OLYMPIC_MEDALISTS WHERE SILVER_COUNTRY IS NOT NULL
UNION ALL
SELECT BRONZE_COUNTRY AS COUNTRY,'B' AS MEDAL
FROM OLYMPIC_MEDALISTS WHERE BRONZE_COUNTRY IS NOT NULL)A
GROUP BY A.COUNTRY)B
ON F.COUNTRY=B.COUNTRY
ORDER BY IFNULL(B.G,0) DESC,IFNULL(B.S,0) DESC,
IFNULL(B.B,0) DESC,IFNULL(B.G+B.S+B.B,0) DESC,F.COUNTRY

SQL compare two rows in a table to find how many values are different

I have the following table for storing user data:
e.g.
TABLE: users
COLUMNS:
...
maritalStatus (INT) - FK
gender (CHAR)
occupation (INT) - FK
...
Now I want to compare two users in this table to see how many columns match for any two given users (say user X & user Y)
I am doing it via mySQL Stored Procedures by getting each value separately and then comparing them
e.g.
SELECT maritalStatus from users where userID = X INTO myVar1;
SELECT maritalStatus from users where userID = Y INTO myVar2;
IF myVar1 = myVar2 THEN
...
END IF;
Is there a shorter way using an SQL query where I can compare two rows in a table and see
which columns are different? I dont need to know how much different they actually are, just
need to know if they contain the same value. Also I will only be comparing selected columns,
not every column in the user table.
This will select the number of columns that are not the same for user x and user y:
SELECT ( u1.martialStatus <> u2.martialStatus )
+ ( u1.gender <> u2.gender )
+ ( u1.occupation <> u2.occupation )
FROM
users u1,
users u2
WHERE u1.id = x
AND u2.id = y
You can also use this:
select
-- add other columns as needed
(a.lastname,a.gender)
= (b.lastname,a.gender) as similar,
a.lastname as a_lastname,
a.firstname as a_firstname,
a.age as a_age,
'x' as x,
b.lastname as b_lastname,
b.firstname as b_firstname,
b.age as b_age
from person a
cross join person b
where a.id = 1 and b.id = 2
Output:
SIMILAR A_LASTNAME A_FIRSTNAME A_AGE X B_LASTNAME B_FIRSTNAME B_AGE
1 Lennon John 40 x Lennon Julian 15
Live test: http://www.sqlfiddle.com/#!2/840a1/2
Just a continued example of Peter Langs suggestion in PHP:
$arr_cols = array('martialStatus', 'gender', 'occupation');
$arr_where = array();
$arr_select = array();
foreach($arr_cols as $h) {
$arr_having[] = "compare_{$h}";
$arr_select[] = "(u1.{$h} != u2.{$h}) AS compare_{$h}";
}
$str_having = implode(' + ', $arr_where);
$str_select = implode(', ', $arr_where);
$query = mysql_query("
SELECT {$str_select}
FROM users AS u1, users AS u2
WHERE u1.userid = {$int_userid_1} AND u2.userid = {$int_userid_2}
HAVING {$str_having} > 0
");
/* Having case can be removed if you need the row regardless. */
/* Afterwards you check these values: */
$row = mysql_fetch_assoc($query);
foreach($arr_cols as $h)
if ($row["compare_{$h}"])
echo "Found difference in column {$h}!";
You can count the number of users with the same columns using group by:
select u1.maritalStatus
, u1.gender
, u1.occupation
, count(*)
from users u1
group by
u1.maritalStatus
, u1.gender
, u1.occupation
I think, this may help someone.
Objective: To find rows with same name and update new records date with the old record. This could be a condition where you will have to duplicate news item for different countryand keep the same date as original.
CREATE TABLE `t` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`locale` varchar(10) DEFAULT 'en',
`title` varchar(255) DEFAULT NULL,
`slug` varchar(255) DEFAULT NULL,
`body` text,
`image` varchar(255) DEFAULT NULL,
`thumb` varchar(255) DEFAULT NULL,
`slug_title` varchar(255) DEFAULT NULL,
`excerpt` text,
`meta_title` varchar(200) DEFAULT NULL,
`meta_description` varchar(160) DEFAULT NULL,
`other_meta_tags` text,
`read_count` int(10) DEFAULT '0',
`status` varchar(20) DEFAULT NULL,
`revised` text,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8;
INSERT INTO `t` (`id`, `locale`, `title`, `slug`, `body`, `image`, `thumb`, `slug_title`, `excerpt`, `meta_title`, `meta_description`, `other_meta_tags`, `read_count`, `status`, `revised`, `created`, `modified`)
VALUES
(2, 'en', 'A title once again', '/news/title-one-again', 'And the article body follows.', '/uploads/2014/11/telecommunications100x100.jpg', NULL, NULL, NULL, '', '', NULL, 0, 'Draft', NULL, '2014-09-22 12:26:17', '2014-10-23 10:13:21'),
(3, 'en', 'A title once again', '/news/title-strikes-back', 'This is really exciting! Not.', '/uploads/2014/11/telecommunications100x100.jpg', NULL, NULL, NULL, '', '', NULL, 0, 'Unpublished', NULL, '2014-09-23 12:26:17', '2014-10-31 11:12:55'),
(4, 'en_GB', 'test', '/news/test', 'test', '/uploads/2014/11/telecommunications100x100.jpg', NULL, NULL, NULL, '', '', NULL, 0, 'Published', NULL, '2014-10-23 10:14:30', '2014-10-23 10:14:30');
update t join
t t2
on t.title = t2.title
set t2.created = t.created
where t.title = t2.title ;
update t join
t t2
on t.title = t2.title
set t2.created = t.created
where t.title = t2.title ;
In the event another Magento developer finds their way here, a specific use for this Q/A is to compare two address entries in a table. "Magento 1" will put the same address in twice with the only differences being the key entity_id column and address_type column ( billing or shipping ).
Already knowing the order's entity_id, use this to get the billing and shipping address IDs associated with the order:
SELECT entity_id FROM sales_flat_order_address WHERE parent_id = 3137;
Then to see if they differ for that order:
SELECT a1.parent_id AS 'order_id'
, ( a1.street <> a2.street )
+ ( a1.city <> a2.city )
+ ( a1.postcode <> a2.postcode )
+ ( a1.region_id <> a2.region_id )
AS 'diffs'
FROM
sales_flat_order_address a1,
sales_flat_order_address a2
WHERE a1.entity_id = 6273
AND a2.entity_id = 6274
;
Gives the output:
+----------+-------+
| order_id | diffs |
+----------+-------+
| 3137 | 0 |
+----------+-------+
It would be fantastic if there was a way to do this en masse.
If you want to find out which columns have different values in two rows of a table, you can use the following query:
SET #table_name = 'YOUR_TABLE_NAME';
SET #primary_column_name = 'PRIMARY_COLUMN_NAME';
SET #row1_id = '1234';
SET #row2_id = '1111';
SELECT CONCAT(
"SELECT ",
(
SELECT GROUP_CONCAT(" (CASE WHEN t1.", column_name, " != t2.", column_name ," THEN t1.", column_name, " ELSE '-' END) AS ", column_name ,"\n") AS all_column_names
FROM information_schema.columns WHERE table_name = #table_name AND table_schema = DATABASE()
), " FROM ",#table_name," AS t1 INNER JOIN Artiklar AS t2 ON(t1.",#primary_column_name," = '",#row1_id,"' AND t2.",#primary_column_name," = '",#row2_id,"')
UNION ALL
SELECT ",
(
SELECT GROUP_CONCAT(" (CASE WHEN t1.", column_name, " != t2.", column_name ," THEN t2.", column_name, " ELSE '-' END) AS ", column_name ,"\n") AS all_column_names
FROM information_schema.columns WHERE table_name = #table_name AND table_schema = DATABASE()
), " FROM ",#table_name," AS t1 INNER JOIN Artiklar AS t2 ON(t1.",#primary_column_name," = '",#row1_id,"' AND t2.",#primary_column_name," = '",#row2_id,"')
")
It will give you a new SQL query that you can run to see which columns have different values between the two rows.