mysql - Order by from multiple tables in Laravel - mysql

I have 3 tables topic1, topic2, topic3. I split into 3 tables because of huge difference in attributes. all of these table has a common column of created_timestamp
I would like to create a timeline displaying from the latest with the created_timestamp as reference.
I have tried joining the tables and order by greatest (t1.created_timestamp, t2.created_timestamp, t3.created_timestamp) but I can't do this in the query builder of laravel.
Plus when displaying of the attributes, they will be repeated, is there a way to only show topic1 attributes if topic1 is the latest, so on and so forth.
Or is there a better method?
Thank you Jedi masters!
I forgot to mention about a fourth table (follower table) that only shows the created_by of the 3 topic tables.
topic1 {var1, var2, created_timestamp, user_id)
topic2 {var3, var2, var3, created_timestamp, teacher_id)
topic3 {var3, created_timestamp, visitor_id)
follower {follow_id, creator_id}
follow_id = user_id, teacher_id, visitor_id (table joins)
I would need to get only rows that I follow (aka where follower.creator_id = $me)

I think the best way would be to run a query for each table and then combine the results.
$a = DB::table('topic1')->join('follower', 'folower.follow_id', '=', 'topic1.user_id')->where('folower.follower_id', $followed)->get();
$b = DB::table('topic2')->join('follower', 'folower.follow_id', '=', 'topic2.teacher_id')->where('folower.follower_id', $followed)->get();
$c = DB::table('topic3')->join('follower', 'folower.follow_id', '=', 'topic3.visitor_id')->where('folower.follower_id', $followed)->get();
$results = new Illuminate\Database\Eloquent\Collection;
$results = $results->merge($a)->merge($b)->merge($c)->sortBy('created_timestamp');

Related

Count quantity of books by category, separate tables

I'm using Laravel 5 and I need to know the number of books by category, these data I will use on google charts.
I have the code below that I use to find out the number of users by sex. However, the data is in the same table.
$data = DB::table('books')
->select(
DB::raw('category_id as category'),
DB::raw('count(*) as number'))
->groupBy('category')
->get();
$array[] = ['Category', 'Number'];
foreach($data as $key => $value)
{
$array[++$key] = [$value->category->name, $value->number];
}
$test = json_encode($array);
Using the same logic as above, how can I get the number of books by category?
I have the Books table:
ID | Name | ID_CATEGORY
1 Laravel 20
2 Java 20
Category table :
ID | Name
20 Programming
Could you help me in this situation?
You should just group by your ID_CATEGORY column instead of sex.
If you want to construct such array as before, you probably want the name of category. That would be easy if you used Eloquent. You would have $value->category->Name_Category available. If you want those preloaded instead of a query for every value, just add ->with('category') to the query and you'll have it.
If you are not using Eloquent, you should just load the category table separately, key it, and use $categories[$value->ID_CATEGORY]->Name_Category.
Other comments
DB::raw('sex as sex') is equivalent to sex.
++$key seems redundant, you could just push to the end by assigning to $array[].
Array with a header row is not very JSON. Typically you'd have key:value pairs all of the time.
You can construct the array using collection methods.
If you don't have good reasons for the opposite, you should stick to Laravel naming conventions.
I would probably not create an array myself, but do your example as something like this:
$stats = User::select('sex', DB::raw('count(*) as number'))
->groupBy('sex')
->get();
$stats->setVisible(['sex', 'number']);
return $stats;

How to join tow times the same table?

I need to join twice the same table how do a do it in Codeigniter?
TABLE MATCH
match_id
team_home_id
team_away_id
score_home
score_away
group_id
round_id
winner
start_date
start_time
TABLE TEAM
team_id
name
code
This are the two tables I have to join. The table team has to join twice with a table match.
$this->db->select('*')
->from('match, team')
->join('team AS team_a', 'match.team_home_id = team_a.team_id')
->join('team AS team_b', 'match.team_away_id = team_b.team_id')
->get()->result();
My result is the match and just one of the teams :/
Thank you, Strawberry for your help.
On my query, I was getting always the last team joined because it looks like Codeigniter overwrites the columns with the same name, team_a.name and team_a.code are represented in the final result_array() as array key "name" and "code" the same as team_b.name and team_b.code.
So more than an alias for the tables I needed to add an alias for the columns:
$this->db->select('tips.stake, tips.odd,
matches.score_home, matches.score_away, matches.winner, matches.start_date, matches.start_time,
team_home.name AS team_home_name, team_home.code AS team_home_code, team_away.name AS team_away_name, team_away.code AS team_away_code');
$this->db->from('tips');
$this->db->join('matches', 'matches.match_id = tips.match_id');
$this->db->join('`teams` `team_home`', 'team_home.team_id = matches.team_home_id');
$this->db->join('`teams` `team_away`', 'team_away.team_id = matches.team_away_id');
$query = $this->db->get();

How to get multiple rows united by common column in mysql in complex select with join

I have 2 tables, one holding pages, the other holding translated values for some of the pages properties:
pages
-id,in_menu,created_at ... etc
page_translations
- id,page_id,locale, title,content... etc
(row for each locale)
I need to create a table with 2 locales - re joining the translation
pages
--- id --- title_en ---- title_he --- in_menu
1 someTitle Translated true
I am using laravel (4.1)
so what i did is:
Page:join('page_translations', 'pages.id', '=', 'page_translations.page_id')
->select(array('pages.id', 'page_translations.page_id as
pageId','page_translations.title as title_en', 'page_translations.title as
title_he','pages.in_menu', 'pages.created_at'))
->where('page_translations.locale','=','en')
->groupBy('page_translations.page_id');
the result sql (for ones that don't dig laravel)
select `pages`.`id`, `page_translations`.`page_id` as `pageId`,
`page_translations`.`title` as `title_en`, `page_translations`.`title` as `title_he`,
pages.in_menu, pages.created_at from pages inner join page_translations on
pages.id = page_translations.page_id where page_translations.locale = 'en'
group by page_translations.page_id
now I have the title twice (as title_en and title_he) and so I used another query to replace the title_he with the right value
PageTranslation::wherePageId($pageId)->whereLocale('he')->pluck('title')
I want to know if anyone can make it in a single query call!
Just join the page_translations table twice:
Page:join('page_translations AS page_translations_en', function($join) {
$join->on('pages.id', '=', 'page_translations_en.page_id'),
$join->on('page_translations_en.locale', '=', 'en')
})->join('page_translations AS page_translations_he', function($join) {
$join->on('pages.id', '=', 'page_translations_he.page_id'),
$join->on('page_translations_he.locale', '=', 'he')
})
->select(array('pages.id AS page_id', 'page_translations_en.title as title_en', 'page_translations_he.title as
title_he','pages.in_menu', 'pages.created_at'))
->groupBy('page_translations.page_id');
Can't guarantee that will work out-of-the-box as I don't have a copy of your schema, but you get the idea.

SQL issue: one to many relationship and EAV model

Good evening guys,
I'm a newbie to web programming and I need your help to solve a problem inherent to SQL query.
The database engine I'm using is MySQL and I access it via PHP, here I'll explain a simplified version of my database, just to fix ideas.
Let's suppose to work with a database containing three tables: teams, teams_information, attributes. More precisely:
1) teams is a table containing some basic information about italian football teams (soccer, not american football :D), it is formed by three fields: 'id' (int, primary key), 'name' (varchar, team name), nickname (Varchar, team nickname);
2) attributes is a table containing a list of possible information about a football team, such as city (the city where team plays its home match), captain (team captain's fullname), f_number (number of fans) and so on. This table is formed by three fields: id (int, primary key), attribute_name (varchar, an identifier for the attribute), attribute_desc (text, an explanation of the meaning of attribute). Each record of this table represents a single possible attribute of a football team;
3) teams_information is a table where some information, about teams listed in team table, are available. This table contains three fields: id (int, primary key), team_id (int, a foreign key which identifies a team), attribute_id (int, a foreign key which identifies one of the attributes listed in attributes table), attribute_value (varchar, the value of the attribute). Each record represents a single attribute of a single team. In general, different teams will have a different number of information, so for some teams a large number of attributes will be available while for other teams only a small number of attributes will be available.
Note that relation between teams and teams_information is one to many and the same relation exists between attributes and teams_information
Well, given this model my purpose is to realize a grid (maybe with ExtJS 4.1) to show user the list of italian football team, each record of this grid will represent a single football team and will contain all possible attributes: some fields may be empty (because, for considered team, the correspondent attribute is unknown), while the others will contain the values stored in teams_information table (for the considered team).
According to the above grid's field are: id, team_name and a number of fields to represent all the different attributes listed in 'attributes' table.
My question is: can I realize such a grid by using a SINGLE SQL query (maybe a proper SELECT query, to fetch all data I need from database tables) ?
Can anyone suggest me how to write a similar query (if it exists) ?
Thanks in advance for helping me.
Regards.
Enrico.
The short answer to your question is no, there is no simple construct in MySQL to achieve the result set you are looking for.
But it is possible to carefully (painstakingly) craft such a query. Here is an example, I trust you will be able to decipher it. Basically, I'm using correlated subqueries in the select list, for each attribute I want returned.
SELECT t.id
, t.name
, t.nickname
, ( SELECT v1.attribute_value
FROM team_information v1
JOIN attributes a1
ON a1.id = v1.attribute_id AND a1.attribute_name = 'city'
WHERE v1.team_id = t.id ORDER BY 1 LIMIT 1
) AS city
, ( SELECT v2.attribute_value
FROM team_information v2 JOIN attributes a2
ON a2.id = v2.attribute_id AND a2.attribute_name = 'captain'
WHERE v2.team_id = t.id ORDER BY 1 LIMIT 1
) AS captain
, ( SELECT v3.attribute_value
FROM team_information v3 JOIN attributes a3
ON a3.id = v3.attribute_id AND a3.attribute_name = 'f_number'
WHERE v3.team_id = t.id ORDER BY 1 LIMIT 1
) AS f_number
FROM teams t
ORDER BY t.id
For 'multi-valued' attributes, you'd have to pull each instance of the attribute separately. (Use the LIMIT to specify whether you are retrieving the first one, the second one, etc.)
, ( SELECT v4.attribute_value
FROM team_information v4 JOIN attributes a4
ON a4.id = v4.attribute_id AND a4.attribute_name = 'nickname'
WHERE v4.team_id = t.id ORDER BY 1 LIMIT 0,1
) AS nickname_1st
, ( SELECT v5.attribute_value
FROM team_information v5 JOIN attributes a5
ON a5.id = v5.attribute_id AND a5.attribute_name = 'nickname'
WHERE v5.team_id = t.id ORDER BY 1 LIMIT 1,1
) AS nickname_2nd
, ( SELECT v6.attribute_value
FROM team_information v6 JOIN attributes a6
ON a6.id = v6.attribute_id AND a6.attribute_name = 'nickname'
WHERE v6.team_id = t.id ORDER BY 1 LIMIT 2,1
) AS nickname_3rd
I use nickname as an example here, because American soccer clubs frequently have more than one nickname, e.g. Chicago Fire Soccer Club has nicknames: 'The Fire', 'La Máquina Roja', 'Men in Red', 'CF97', et al.)
NOT AN ANSWER TO YOUR QUESTION, BUT ...
Have I mentioned numerous times before, how much I dislike working with EAV database implementations? What should IMO be a very simple query turns into an overly complicated beast of a potentially light dimming query.
Wouldn't it be much simpler to create a table where each "attribute" is a separate column? Then queries to return reasonable result sets would look more reasonable...
SELECT id, name, nickname, city, captain, f_number, ... FROM team
But what really makes me shudder is the prospect that some developer is going to decide that the LDQ should be "hidden" in the database as a view, to enable the "simpler" query.
If you go this route, PLEASE PLEASE PLEASE resist any urge you may have to store this query in the database as a view.
I'm going to take a slightly different route. Spencer's answer is fantastic, and it addresses the issue quite well, but there's still a large underlying problem.
The data that you are trying to display on the site is over-normalized in the database. I won't elaborate, since, again, Spencer's answer highlights the issue pretty well.
Rather, I'd like to recommend a solution that denormalizes the data a bit.
Convert all of your Team data into a single table with many columns. (If there is Player data that isn't covered in the question, that would be a second table, but I'll gloss over that for now.)
Sure, you'll have a whole bunch of columns, and a lot of the columns might be NULL for a lot of the rows. It's not normalized, and it's not pretty, but here's the huge advantage that you gain.
Your query becomes:
SELECT * FROM Teams
That's it. That gets displayed right to the website and you are done. You might have to go out of your way to realize this schema, but it would be totally worth the time investment.
I think what you're saying is that you want the rows in the attributes table to appear as columns in the result recordset. If this is correct, then then in SQL you would use PIVOT.
A quick search on SO seems to indicate that there is no PIVOT equivalent in MySql.
I wrote a simple PHP script to generalize spencer's idea to solve my issue.
Here's the code:
<?php
require_once('includes/db.config.php'); //this file performs connection to mysql
/*
* Following function requires a table name ($table)
* and a number of service fields ($num). Given those parameters
* it returns the number of table fields (excluding service fields).
*/
function get_fields_number($table,$num,$conn)
{
$query = "SELECT * FROM $table";
$result = mysql_query($query,$conn);
return mysql_num_fields($result)-$num; //remember there are $num service fields
}
/*
* Following function requires a table name ($table) and an array
* containing a list of service fields names. Given those parameters,
* it returns the list of field names. That list is contained within an array and
* service fields are excluded.
*/
function get_fields_name($table,$service,$conn)
{
$query = "SELECT * FROM $table";
$result = mysql_query($query,$conn);
$name = array(); //Array to be returned
for ($i=0;$i<mysql_num_fields($result);$i++)
{
if(!in_array(mysql_field_name($result,$i),$service))
{
//currently selected field is not a service field
$name[] = mysql_field_name($result,$i);
}
}
return $name;
}
//Below $conn is db connection created in 'db.config.php'
$query = "SELECT `name` FROM `detail_arg` WHERE visibility = 0";
$res = mysql_query($query,$conn);
if($res===false)
{
$err_msg = mysql_real_escape_string(mysql_error($conn));
echo "{success:false,data:'".$err_msg."'}";
die();
}
$arg = array(); //list of argument names
while($row = mysql_fetch_assoc($res))
{
$arg[] = $row['name'];
}
//Following function writes the select subquery which is
//necessary to build a column containing a single attribute.
function make_subquery($attribute) //$attribute contains attribute name
{
$query = "";
$query.="(SELECT incident_detail.arg_value ";
$query.="FROM incident_detail ";
$query.="INNER JOIN detail_arg ";
$query.="ON incident_detail.arg_id = detail_arg.id AND detail_arg.name='".$attribute."' ";
$query.="WHERE incident.id = incident_detail.incident_id) ";
$query.="AS $attribute";
return $query;
}
/*
echo make_subquery("date"); //debug code
*/
$subquery = array(); //list of subqueries
for($i=0;$i<count($arg);$i++)
{
$subquery[] = make_subquery($arg[$i]);
}
$query = "SELECT "; //final query containing subqueries
$fields = get_fields_name("incident",array("id","visibility"),$conn);
//list of 'incident' table's fields
for($i=0;$i<count($fields);$i++)
{
$query.="incident.".$fields[$i].", ";
}
//insert the subqueries
$sub = implode($subquery,", ");
$query .= $sub;
$query.=" FROM incident ORDER BY incident.id";
echo $query;
?>

Doctrine issue - Different queries, same results but not with Doctrine

i'm having a little issue with doctrine using symfony 1.4 (I think it's using doctrine 1.2). I have 2 queries, using raw sql in the mysql console, they produce the same resultset. The queries can be generated using this code :
$dates = Doctrine::getTable('Picture')
->createQuery('a')
->select('substr(a.created_at,1,10) as date')
->leftjoin('a.PictureTag pt ON a.id = pt.picture_id')
->leftjoin('pt.Tag t ON t.id = pt.tag_id')
->where('a.created_at <= ?', date('Y-m-d 23:59:59'))
->orderBy('date DESC')
->groupby('date')
->limit(ITEMS_PER_PAGE)
->offset(ITEMS_PER_PAGE * $this->page)
->execute();
If I remove the two joins, it changes the query, but the resultset it's the same.
But using doctrine execute(), one produces only one row.
Somebody have an idea on what's going on here?
PS : Picture table has id, title, file, created_at (format 'Y-m-d h:i:s'), the Tag table is id, name and PictureTag is an relationship table with id and the two foreign keys.
PS 2 : Here are the two sql queries produced (the first without joins)
SELECT substr(l.created_at, 1, 10) AS l__0 FROM lupa_picture l WHERE (l.created_at <= '2010-03-19 23:59:59') GROUP BY l__0 ORDER BY l__0 DESC LIMIT 4
SELECT substr(l.created_at, 1, 10) AS l__0 FROM lupa_picture l LEFT JOIN lupa_picture_tag l2 ON (l.id = l2.picture_id) LEFT JOIN lupa_tag l3 ON (l3.id = l2.tag_id) WHERE (l.created_at <= '2010-03-19 23:59:59') GROUP BY l__0 ORDER BY l__0 DESC LIMIT 4
I had something similar this week. Doctrine's generated SQL (from the Symfony debug toolbar) worked fine in phpMyAdmin, but failed when running the query as in your question. Try adding in the following into your query:
->setHydrationMode(Doctrine::HYDRATE_SCALAR)
and see if it gives you the expected result. If so, it's down to the Doctrine_Collection using the Picture primary key as the index in the collection. If you have more than 1 result with the same index, Doctrine will refuse to add it into the collection, so you only end up with 1 result. I ended up running the query using a different table rather than the one I wanted, which resulted in a unique primary key and then the results I wanted appeared.
Well, the solution was that...besides substr(), it needs another column of the table. Using select(substr(), a.created_at) made it work