SQL join and group items as an array - mysql

I have the following SQL
SELECT articles.id, articles.title, tags.name AS tags
FROM articles
LEFT JOIN article_tag_association ON articles.id = article_tag_association.article_id
LEFT JOIN tags ON tags.id = article_tag_association.tag_id
This works OK except it creates a row for each tag an article has, which messes with the limit
e.g.
[
"0" => ["id" => "1", "title" => "test", "tags" => "tag1"],
"1" => ["id" => "1", "title" => "test", "tags" => "tag2"],
"2" => ["id" => "2", "title" => "test2", "tags" => "tag1"],
]
(only 2 articles but three rows)
is there a way to make it return each article with an array of tags?
something like:
[
"0" => ["id" => "1", "title" => "test", "tags" => ["tag1", "tag2"]],
"1" => ["id" => "2", "title" => "test2", "tags" => ["tag1"]],
]

by default you cannot return an array. but you can decorate/concatenate your columns to produce an array like string. if its a good idea? depends on your situation. Also please be aware MySQL has some limitations for group_concat (will only return 1024*chars)
anyway just for test purpose you can try this:
SELECT
concat(
'[',
concat('ID => "', articles.id,'"'),
concat('Title => "', articles.title,'"'),
concat('Tags => [', GROUP_CONCAT(concat('"',tags.name, '"')), ']'),
']'
) as Array_String
FROM
articles
LEFT JOIN
article_tag_association ON articles.id = article_tag_association.article_id
LEFT JOIN
tags ON tags.id = article_tag_association.tag_id
GROUP BY articles.id
this will give you each row as an array, if you want everything in one line put them all under a group_concat.
note: if your result is larger than 1024 char you have to use
SET group_concat_max_len = 1000000; >> size of your string length
PS: haven't tested above code. test it :)

SELECT articles.id, articles.title, GROUP_CONCAT(tags.name) AS tags
FROM articles
LEFT JOIN article_tag_association ON articles.id = article_tag_association.article_id
LEFT JOIN tags ON tags.id = article_tag_association.tag_id
GROUP BY articles.id
You can't return an array in mysql, but you can get this concatenated string and split it into an array on PHP side. You can choose an character used for 'glue' by GROUP_CONCAT(name SEPARATOR '#') for a one that should not appear in any name and thus be safe for splitting into the array.

Related

How to use an operator in where clause with an optional include?

I have the following Model:
AuthorModel.hasMany(BookModel);
BookModel.belongsTo(AuthorModel);
Some authors have no books.
I want to select an author whose name or title of one of his books matches the search string.
I can achieve this with the following statement, but only for authors with books in their BookModel
Author.findOne({
include: [{
model: Book,
where: {
[Op.or]: [
{'$author.name$': 'search string'},
{ title: 'search string'}
]
},
}]
})
This gives me more or less the following mysql query:
SELECT
`author`.`name`,
`book`.`title`
FROM `author` INNER JOIN `book`
ON `author`.`id` = `book`.`authorId`
AND ( `author`.`name` = 'search string' OR `book`.`title` = 'search string');
The problem here is, if an author has no books, then the result is empty. Even if there is an author that matches the search criteria.
I tried to set the include to required: false, which gives a left outer join. In that case, I get some not matching results. The where clause is omitted.
How do I have to change my sequelize query, or what would be the proper mysql query?
The MySql query should probably be something like
SELECT
`author`.`name`,
`book`.`title`
FROM `author` LEFT JOIN `book`
ON `author`.`id` = `book`.`authorId`
WHERE ( `author`.`name` = 'search string' OR `book`.`title` = 'search string')
Note how here filtering condition is in WHERE rather than part of JOIN ... ON clause
Basing on the example at Top level where with eagerly loaded models you squizzle query should probably be something like
Author.findOne({
where: {
[Op.or]: [
{'$author.name$': 'search string'},
{ '$Book.title$': 'search string'}
]
},
include: [{
model: Book,
required: false
}]})

query including many tables cakephp 3

Using cakephp 3 I want to have the same result that I will have if I execute the query below (Get all paiements of appartements that belongs to Complex XX) :
SELECT p.*
FROM immeubles i inner join appartements a on i.id=a.immeuble_id
inner join paiements p on p.appartement_id = a.id
where i.complex_id=4
tables used are :
Immeubles (id,name,complex_id(Foreign Key));
Appartements(id, num,immeuble_id(foreign key))
paiements(id,montant,appartement_id(foreign key))
I tried first to extract all appartements that belongs to complex=4 by using the code below but i don't know how to concatenate the results with the table Paiements in order to show all fields of paiements :
$this->Appartements->find()
->join([
'i' => [
'table' => 'Immeubles',
'type' => 'inner',
'conditions' => [
'i.id=Appartements.immeuble_id',
'i.complex_id' => 4
]]]);

CakePHP 3.0 how to contain a left outer join

Suppose that Candidates has one field named Ratings.
The association is optional.
I want all Candidates having a rating other than 0 or no rating at all
and to contain the ratings in the result.
I managed to get the right query for the first part by coding a left outer join like this:
$this->Candidates
->find('all')
->leftJoin(
['Ratings' => 'ratings'],
[
'Ratings.candidate_id = Candidates.id',
'Ratings.rating = 0',
],
['Ratings.rating' => 'integer'])
->where('Ratings.id IS NULL');
However I failed to get the rating contained within the result. How can that be done?
Did you add contain() to the query?
$this->Candidates
->find('all')
->contain('Ratings')
->leftJoin(
['Ratings' => 'ratings'],
[
'Ratings.candidate_id = Candidates.id',
'Ratings.rating = 0',
],
['Ratings.rating' => 'integer'])
->where('Ratings.id IS NULL');

Mysql Join rows from multiple tables

I am asking here because what english words i need to search for this what i want to do.
I can solve this with multiple queries then join arrays with for and foreach loops but why to do it if there is smarter way.
My query now looks like:
SELECT p.* , pi.path, pv.videoid
FROM products p
JOIN productimage pi ON (p.id = pi.product)
JOIN productvideo pv ON (p.id = pv.product)
WHERE p.id=:id
my tables look like
product:
|---id---|---name----|---desc---|---price---|---active----|
|---1----|---somet---|---dsa----|---456-----|---1/0-------|
|---2----|---somet2--|---ddsasa-|---44556---|---1/0-------|
product video:
|----id----|---product----|----videoid-------|
|----4-----|---1----------|-----youtubeid----|
|----4-----|---1----------|--secondyoutubeid-|
and same as video for images, and i want to make same for files
result like this to make it easy
//result of $stmt->fetchAll(PDO::FETCH_ASSOC);
$arr = array(
0 => array(
"id" => "1",
"name" => "hello world",
"category" => "3",
"videoid" => array("oneyoutubeid", "secondid", "thirdid"), //videos that is for this product
"path" => array("imagepathofproduct", "secondimageofproduct"),
"active" => "1"
),
1 => array(
"id" => "2",
"name" => "hello world product 2",
"category" => "4",
"videoid" => array("oneyoutubeid", "secondid", "thirdid"), //videos that is for this product
"path" => array("imagepathofproduct", "secondimageofproduct"),
"active" => "1"
),
);
to use it like this: return $stmt->fetchAll(PDO::FETCH_ASSOC); and start foreach for displaying data.
thankyou very much
solved by concat and i will explode it into array.
but still waiting for more smart solutions.
Solution:
SELECT p.* , GROUP_CONCAT(DISTINCT path ORDER BY pi.id) AS images, GROUP_CONCAT(DISTINCT videoid ORDER BY pi.id) AS videos FROM products p JOIN productimage pi ON (p.id = pi.product) JOIN productvideo pv ON (p.id = pv.product) WHERE p.id=1
Home it will helpt to somebody

Merging Many to Many Results

I'm using the following SELECT statement:
SELECT *
FROM prefix_site_tmplvars
LEFT JOIN prefix_site_tmplvar_contentvalues
ON prefix_site_tmplvar_contentvalues.tmplvarid = prefix_site_tmplvars.id
LEFT JOIN prefix_site_content
ON prefix_site_tmplvar_contentvalues.contentid = prefix_site_content.id
WHERE prefix_site_tmplvar_contentvalues.value = "chocolate"
This is what I get back:
[id] => 2
[name] => flavor
[value] => chocolate
[id] => 2
[name] => type
[value] => cookie
This is the result I'd like to get:
[id] => 2
[flavor] => chocolate
[type] => cookie
Is there a way to combine my results so I don't have a bunch of rows referring to the same ID? If now, how should I handle this?
I'm using Modx and this is working with the Template Variable tables: http://wiki.modxcms.com/index.php/Template_Variable_Database_Tables
You can just use case statements:
SELECT
id,
MAX( CASE WHEN name = 'flavor' THEN value ELSE NULL END ) AS flavor,
MAX( CASE WHEN name = 'type' THEN value ELSE NULL END ) AS type
FROM prefix_site_tmplvars
LEFT JOIN prefix_site_tmplvar_contentvalues
ON prefix_site_tmplvar_contentvalues.tmplvarid = prefix_site_tmplvars.id
LEFT JOIN prefix_site_content
ON prefix_site_tmplvar_contentvalues.contentid = prefix_site_content.id
WHERE prefix_site_tmplvar_contentvalues.value = "chocolate"
GROUP BY id
Of course, this approach only works if you know ahead of time what keys you want to select; but it seems like in this case you do know (flavor + type).
Problem is, you might have
{ "id": 2, "flavor": "chocolate", "type": "cookie" }
and another row with
{ "id": 3, "flavor": "vanilla", "calories": "375" }
...I don't think there's an easy way to solve the problem in MySQL; you'd need to decide what keys to look for.
On the other hand you can collapse the rows in PHP:
while($tuple = ...fetch tuple from mysql cursor...)
{
list ($id, $name, $value) = $tuple;
if (!isset($objs[$id]))
{
// You might want to add also "'id' => $id" to the array definition
$objs[$id]= array ($name => $value);
}
else
{
$obj = $objs[$id];
$obj[$name] = $value;
$objs[$id] = $obj;
}
}
Now $objs contains the desired data; you still need to get it back into Modx, though.
You can do this with a group by:
SELECT id,
max(case when name = 'flavor' then value end) as flavor,
max(case when name = 'type' then value end) as type
FROM prefix_site_tmplvars LEFT JOIN
prefix_site_tmplvar_contentvalues
ON prefix_site_tmplvar_contentvalues.tmplvarid = prefix_site_tmplvars.id LEFT JOIN
prefix_site_content ON prefix_site_tmplvar_contentvalues.contentid = prefix_site_content.id
WHERE prefix_site_tmplvar_contentvalues.value = "chocolate"
group by id