I have one games table and two tables to store their scores, game_scores and thirdparty_game_scores with exactly the same structure, something like that:
-------------------------GAMES-------------------------
slug | title | technology | (other non-relevant fields)
-------------------------------------------------------
----------------------GAME SCORES----------------------
id (PK) | game_slug | user | team | score | timestamp
-------------------------------------------------------
----------------THIRDPARTY GAME SCORES-----------------
id (PK) | game_slug | user | team | score | timestamp
-------------------------------------------------------
And their Models like:
class GameScores extends BaseGamesModel {
public function initialize() {
parent::initialize();
$this->belongsTo( 'game_slug', 'Games', 'slug' );
$this->skipAttributes(array('creation_time', 'last_modified'));
}
}
I'm building a weekly ranking with game scores stored only on game_scores:
$ranking = GameScores::sum(
array(
"column" => "score",
"conditions" => $conditions,
"group" => "team",
"order" => "sumatory DESC"
)
);
Is there any way to create a Model that concatenates all data from game_scores and thirdparty_game_scores to create the same ranking without any extra effort?
Thank you so much!
You can set the source table dynamically in your model. See Can I build a Model for two Tables on PhalconPHP?
You should use the setSource() method and put your condition in there, e.g.:
$source = 'default_table';
if (class_name($this) === 'A') {
$source = 'A_table';
}
return $source;
Related
I'm working on a project with Laravel and Eloquent/MySQL.
I'd like to know how to handle a many-to-many relationship with three tables (users, merchants, roles).
Any user can have one or more merchant.
Any merchant can be shared between users.
Any user have a specific role for a merchant.
Is there a best practice to follow?
How can I get all the merchants by a user having a specific role?
Thanks for helping
This is my current structure:
Users
-------------------------------
| id | first_name | last_name |
-------------------------------
Merchants
-------------------
| id | trade_name |
-------------------
Roles
-------------------------
| id | name | hierarchy |
-------------------------
merchant_user_role
-----------------------------------
| merchant_id | user_id | role_id |
-----------------------------------
Merchant Model
<?php
class Merchant extends Model
{
public function users()
{
return $this->belongsToMany('App\User', 'merchant_user_role')
->withPivot('role_id')
->withTimestamps()
->orderBy('first_name', 'asc');
}
}
User Model
<?php
class User extends Model
{
public function merchants()
{
return $this->belongsToMany('App\Merchant', 'merchant_user_role')
->withPivot('role_id')
->withTimestamps()
->orderBy('trade_name', 'asc');
}
public function roles()
{
return $this->belongsToMany('App\Role', 'merchant_user_role')
->withTimestamps();
}
}
Role Model
<?php
class Role extends Model
{
public function users()
{
return $this->belongsToMany('App\User')->withTimestamps();
}
}
$merchants = $user->merchants()->where('role_id', $some_role_id)->get();
When I was delete record Users, the ID primary key auto increment always jumping, I want ID auto increment is sequence again, this sample
| ID | UserName |
| 1 | Budi |
| 2 | Joko |
| 3 | Amir |
when I delete users Joko, then I add new other user, the ID number is jumping
| ID | UserName |
| 1 | Budi |
| 3 | Amir |
| 4 | Faris |
while I've browsing solution, I get some solution, but doesn't work.
here I've add modified file
config/app.php
'SQLkonek' => [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Mysql',
'persistent' => false,
'host' => 'localhost',
'username' => 'root',
'paassword' => ' ',
'database' => 'klinikucing',
'encoding' => 'utf8mb4',
'timezone' => ' ',
'cacheMetadata' => true
]
then I call modified above through
controller/UsersController.php
public function delete ($id = null)
{
$this->request->allowMethod(['post', 'delete']);
$kon = ConnectionManager::get('SQLkonek');
$user = $this->Users->get($id);
$stm = $kon->execute(array(
['SET #count = 0'],
['DELETE FROM users WHERE ID = :ID'],
['UPDATE users SET users.ID = #count:= #count + 1'],
['ALTER TABLE users AUTO_INCREMENT =1']
))
->fetchAll('assoc');
If($this->Users->$stm) {
$this->Flash->success(__('Users success delete.'));
} else {
$this->Flash->error(__('User delete failed, try again.'));
}
return $this->redirect(['action' => 'index']);
}
The error message was shown
Warning (2): PDO::prepare() expects parameter 1 to be string, array given [CORE\src\Database\Driver\Mysql.php, line 138]
Warning (512): Unable to emit headers. Headers sent in file=C:\xampp\htdocs\klinikucing\vendor\cakephp\cakephp\src\Error\Debugger.php line=853 [CORE\src\Http\ResponseEmitter.php, line 48]
Warning (2): Cannot modify header information - headers already sent by (output started at C:\xampp\htdocs\klinikucing\vendor\cakephp\cakephp\src\Error\Debugger.php:853) [CORE\src\Http\ResponseEmitter.php, line 148
Warning (2): Cannot modify header information - headers already sent by (output started at C:\xampp\htdocs\klinikucing\vendor\cakephp\cakephp\src\Error\Debugger.php:853) [CORE\src\Http\ResponseEmitter.php, line 181]
Argument 1 passed to Cake\Database\Statement\PDOStatement::__construct() must be an instance of PDOStatement or null, boolean given, called in C:\xampp\htdocs\klinikucing\vendor\cakephp\cakephp\src\Database\Driver\Mysql.php on line 139
Error in: ROOT\vendor\cakephp\cakephp\src\Database\Statement\PDOStatement.php, line 33
My CakePHP version is 3.7.2
I hopefull that someone can help me, thanx
Don't renumber. Accept there will be gaps in AI due to deleting an other cause (like aborted transaction, INSERT IGNORE etc.).
As you change PK values you change the FK relationships they have.
With ID of a type of INT UNSIGNED or BIGINT UNSIGNED you're not going to run out of ID ever.
I'm using MySQL and i have schema like:
|------------|-------------|------------|--------------|
| cities |category_city| categories| companies |
|------------|-------------|------------|--------------|
| id | city_id | id | id |
| name | category_id | name |subcategory_id|
| | | parent_id | city_id |
| | | |...other cols |
|____________|_____________|____________|______________|
Relationships:
City with Category has ->belongsToMany()
public function categories()
{
return $this->belongsToMany(Category::class);
}
Categories has subcategories:
public function subcategories()
{
return $this->hasMany(Category::class, 'parent_id', 'id');
}
And i'm getting companies from category and filtering by city, because i need the current city companies and for that i have a global scope:
public function getCompanies($city_id)
{
return $this->companies()->whereHas('mainCity', function ($q) use ($city_id) {
$q->where('city_id', $city_id);
});
}
mainCity method:
public function mainCity()
{
return $this->belongsTo(City::class, 'city_id');
}
Here is my method performing the query with AJAX request:
public function getPlaces(City $city, Category $subcategory, $north, $south, $east, $west)
{
$companies = $subcategory->companies()
->withCount('comments')
->companiesByBounds($north, $south, $east, $west)
->paginate(8);
$markers = $subcategory->companies()
->companiesByBounds($north, $south, $east, $west)
->get(['lat', 'lng', 'slug', 'title']);
return response()->json(['companies' => $companies, 'markers' => $markers], 200, [], JSON_NUMERIC_CHECK);
}
and by companiesByBounds scope method:
public function scopeCompaniesByBounds($query, $north, $south, $east, $west)
{
return $query->whereBetween('lat', [$south, $north])
->whereBetween('lng', [$west, $east]);
}
In companies i have ~2m records. The main problem is that the queries taking 3.5 seconds. Help please to improve my queries.
Here is the query:
select count(*) as aggregate from `companies` where `companies`.`category_id` = '40' and `companies`.`category_id` is not null and `lat` between '53.68540097020851' and '53.749703253622705' and `lng` between '91.34262820463869' and '91.51600619536134'
To improve speed you need to add indexes on the columns lat and lng.
CREATE INDEX idx_lat ON companies (lat);
The indexes are used in queries when the columns are added to conditions.
I am building a popover, in which you can tick checkboxes. The options and choices are stored in a manytomany relationship inside a mysql database.
[ ] option A
[x] option B
[ ] option C
There are 3 tables. sphotos, sphoto_feedback and sphoto_has_feedbacks. sphoto_has_feedbacks stores a sphoto_id, a sphoto_feedback_id and a user_id, to reference the user that has submitted the voting.
sphoto
id | status_id | ...
1 | ...
sphoto_feedback
id | name | ...
11 | Quality |
12 | Creative |
sphoto_has_feedbacks
id | sphoto_id | sphoto_feedback_id | user_id
1 | 1 | 11 | 9999
The Input is user_id => 9999 and sphoto_id => 1. The desired output would be an array, which has all sphoto_feedback entrys, with a boolean variable, like this:
$output = [
"0" => [
"id" => 11,
"name" => "Quality",
"checked" => true
],
"1" => [
"id" => 12,
"name" => "Creative",
"checked" => false
]
]
It would look like this:
[x] Quality <-- stored in sphoto_feedback, also stored in sphoto_has_feedbacks with reference to user
[ ] Creative <-- stored in sphoto_feedback
I want to retrieve all the options from the database and check, if the user has already voted on the options or not.
I know how to do it in PHP with 2 querys, but I'd like to use just one query and would like to know if this is possible.
Use a LEFT JOIN to join the sphoto_feedback and sphoto_has_feedback tables, returning NULL for all the rows in the first table that don't have a match in the second table.
SELECT f.id, f.name, shf.id IS NOT NULL AS checked
FROM sphoto_feedback AS f
LEFT JOIN sphoto_has_feedback AS shf
ON f.id = shf.sphoto_feedback_id
AND shf.sphoto_id = 1 AND shf.user_id = 9999
I have a table like this:
+--------+--------------------+
| ID | Attribute |
+--------+--------------------+
| 1 |"color" => "red" |
+--------+--------------------+
| 1 |"color" => "green" |
+--------+--------------------+
| 1 |"shape" => "square" |
+--------+--------------------+
| 2 |"color" => "blue" |
+--------+--------------------+
| 2 |"color" => "black" |
+--------+--------------------+
| 2 |"flavor" => "sweat" |
+--------+--------------------+
| 2 |"flavor" => "salty" |
+--------+--------------------+
And I want to run some postgres query that get a result table like this:
+--------+------------------------------------------------------+
| ID | Attribute |
+--------+------------------------------------------------------+
| 1 |"color" => "red, green", "shape" => "square" |
+--------+------------------------------------------------------+
| 2 |"color" => "blue, black", "flavor" => "sweat, salty" |
+--------+------------------------------------------------------+
The attribute column can either be hstore or json format. I wrote it in hstore for an example, but if we cannot achieve this in hstore, but in json, I would change the column to json.
I know that hstore does not support one key to multiple values, when I tried some merge method, it only kept one value for each key. But for json, I didn't find anything that supports multiple value merge like this neither. I think this can be done by function merging values for the same key into a string/text and add it back to the key/value pair. But I'm stuck in implementing it.
Note: if implement this in some function, ideally any key such as color, shape should not appear in the function since keys can be expanded dynamically.
Does anyone have any idea about this? Any advice or brainstorm might help. Thank you!
Just a note before anything else: in your desidered output I would use some proper json and not that kind of lookalike. So a correct output according to me would be:
+--------+----------------------------------------------------------------------+
| ID | Attribute |
+--------+----------------------------------------------------------------------+
| 1 | '{"color":["red","green"], "flavor":[], "shape":["square"]}' |
+--------+----------------------------------------------------------------------+
| 2 | '{"color":["blue","black"], "flavor":["sweat","salty"], "shape":[]}' |
+--------+----------------------------------------------------------------------+
A PL/pgSQL function which parses the json attributes and executes a dynamic query would do the job, something like that:
CREATE OR REPLACE FUNCTION merge_rows(PAR_table regclass) RETURNS TABLE (
id integer,
attributes json
) AS $$
DECLARE
ARR_attributes text[];
VAR_attribute text;
ARR_query_parts text[];
BEGIN
-- Get JSON attributes names
EXECUTE format('SELECT array_agg(name ORDER BY name) AS name FROM (SELECT DISTINCT json_object_keys(attribute) AS name FROM %s) AS s', PAR_table) INTO ARR_attributes;
-- Write json_build_object() query part
FOREACH VAR_attribute IN ARRAY ARR_attributes LOOP
ARR_query_parts := array_append(ARR_query_parts, format('%L, array_remove(array_agg(l.%s), null)', VAR_attribute, VAR_attribute));
END LOOP;
-- Return dynamic query
RETURN QUERY EXECUTE format('
SELECT t.id, json_build_object(%s) AS attributes
FROM %s AS t,
LATERAL json_to_record(t.attribute) AS l(%s)
GROUP BY t.id;',
array_to_string(ARR_query_parts, ', '), PAR_table, array_to_string(ARR_attributes, ' text, ') || ' text');
END;
$$ LANGUAGE plpgsql;
I've tested it and it seems to work, it returns a json with. Here is my test code:
CREATE TABLE mytable (
id integer NOT NULL,
attribute json NOT NULL
);
INSERT INTO mytable (id, attribute) VALUES
(1, '{"color":"red"}'),
(1, '{"color":"green"}'),
(1, '{"shape":"square"}'),
(2, '{"color":"blue"}'),
(2, '{"color" :"black"}'),
(2, '{"flavor":"sweat"}'),
(2, '{"flavor":"salty"}');
SELECT * FROM merge_rows('mytable');
Of course you can pass the id and attribute column names as parameters as well and maybe refine the function a bit, this is just to give you an idea.
EDIT : If you're on 9.4 please consider using jsonb datatype, it's much better and gives you room for improvements. You would just need to change the json_* functions to their jsonb_* equivalents.
If you just want this for display purposes, this might be enough:
select id, string_agg(key||' => '||vals, ', ')
from (
select t.id, x.key, string_agg(value, ',') vals
from t
join lateral each(t.attributes) x on true
group by id, key
) t
group by id;
If you are not on 9.4, you can't use the lateral join:
select id, string_agg(key||' => '||vals, ', ')
from (
select id, key, string_agg(val, ',') as vals
from (
select t.id, skeys(t.attributes) as key, svals(t.attributes) as val
from t
) t1
group by id, key
) t2
group by id;
This will return:
id | string_agg
---+-------------------------------------------
1 | color => red,green, shape => square
2 | color => blue,black, flavor => sweat,salty
SQLFiddle: http://sqlfiddle.com/#!15/98caa/2