I installed new version of MariaDB and got problem with sorting php-script:
$this->db->query("
SET #v:=0;
UPDATE `users` AS `c1`
LEFT JOIN (SELECT `id`, (#v:=#v+2) AS `ord2`, `name`, `parent`
FROM `users`
WHERE `parent`='0' ORDER BY `ord` ASC) AS `c2`
ON `c1`.`id` = `c2`.`id`
SET `c1`.`ord` = `c2`.`ord2`
WHERE `c1`.`parent` = '0'
");
In particular script sets order of entries in users table sorting them by ord:
+----+-----+--------------------+--------+
| id | ord | name | parent |
+----+-----+--------------------+--------+
| 2 | 2 | admin | 0 |
| 10 | 5 | manager | 0 |
| 12 | 7 | user | 0 |
| 11 | 9 | dev | 0 |
+----+-----+--------------------+--------+
I'm not familiar with SQL and after long hours of searching and tests I, as it seems to me, found out the SQL-query that doesn't work right:
SELECT `id`, (#v:=#v+2) AS `ord2`, `name`, `parent`
FROM `users`
WHERE `parent`='0' ORDER BY `ord` ASC;
In previous version of MariaDB (5.5.5-10.1.25) the query gives entries sorted by initial order (ord):
+----+-----+--------------------+--------+
| id | ord2| name | parent |
+----+-----+--------------------+--------+
| 2 | 2 | admin | 0 |
| 10 | 4 | manager | 0 |
| 12 | 6 | user | 0 |
| 11 | 8 | dev | 0 |
+----+-----+--------------------+--------+
In new version (10.6.3) result is:
+----+-----+--------------------+--------+
| id | ord2| name | parent |
+----+-----+--------------------+--------+
| 2 | 2 | admin | 0 |
| 10 | 4 | manager | 0 |
| 12 | 8 | user | 0 |
| 11 | 6 | dev | 0 |
+----+-----+--------------------+--------+
I tried to set ORDER BY param ord to name and id, but result was the same. It seems like col ord2 filled with even numbers is always sorted by id in ascending order.
How should I rewrite the query or the script to fix the problem?
In latest MariaDB versions you can use window function row_number in next way:
select
id,
(row_number() over (order by ord))*2 ard2,
name,
parent
from tbl;
MariaDB query test here
Related
I have built a membership application that allows users to assemble projects whose contents are contained across 2 tables ('projects' and 'notes'). Each member can create, update or delete as many projects as they want.
Good so far...
I'd like the members to be able to share their projects with other members they choose. At this point I have built a function that allows Member A to type in an email address in order to share a project (with say, Member B). If that email exists in the DB it updates a third table 'sharing' with the project owner's ID (Member A), the "shared_with" member's ID (Member B) and the project ID. (Perhaps I have gone bullheaded in the wrong direction?)
The problem: How do I query the DB to show Member B all of their own projects + any projects that have been shared with them? The query below illustrates the direction I've been which has been useless. I am trying to say, "Select all from projects where user_id = (me) AND all corresponding projects where my ID is in the 'sharing' table under the 'shared_with' column. ...Oh yeah, and grab that project_id in order to know which project while you're at it."
My brain is mush. Any direction would be sincerely appreciated.
function find_all_projects($id) {
global $db;
$sql = "
SELECT *
FROM projects
LEFT
JOIN sharing
on projects.id = sharing.project_id
WHERE user_id = '" . db_escape($db, $id) . "'
OR sharing.shared_with = '" . db_escape($db, $id) . "'
ORDER
BY project_name
";
$result = mysqli_query($db, $sql);
confirm_result_set($result);
return $result;
}
Current Table Structure
From your question I believe your current table structure to be something like the following:
TABLE: user TABLE: project TABLE: shared
id | email | | id | user_id | content | | id | user_id | project_id
---+-------------------- ---+---------+------------------------------ ---+---------+------------
1 | james#website.com | | 1 | 1 | Project for James | | 9 | 1 | 5
2 | hannah#website.com | | 2 | 1 | Some other project for James | | 10 | 3 | 5
3 | lucy#website.com | | 3 | 2 | Project for Hannah | | 11 | 1 | 8
| | | 4 | 2 | A new project for hannah | | 12 | 2 | 8
| | | 5 | 2 | Hannah's pride and Joy | |
| | | 6 | 3 | Lucy cracking down | |
| | | 7 | 3 | Lucy's second project | |
| | | 8 | 3 | Lucy's public stuff | |
SQL
Example: https://www.db-fiddle.com/f/6KnEsGUmy5PS42usmzyTEX/0
SELECT project.id, project.user_id AS owner_id, shared.user_id AS shared_id, project.content
FROM project
LEFT JOIN shared
ON project.id = shared.project_id
AND project.user_id <> ?
WHERE project.user_id = ?
OR shared.user_id = ?;
N.B.
The key difference between this SQL statement and the one in your question is
AND project.user_id <> ?
Without that condition in the ON clause you will get duplicate records for every shared project for that user. I.e. if the user has shared the project with 20 users then there will be 20 duplicates.
This is expected behaviour as explained here: PHP while statement echoes duplicates
PHP
$sql = "
SELECT project.id, project.user_id AS owner_id, shared.user_id AS shared_id, project.content
FROM project
LEFT JOIN shared
ON project.id = shared.project_id
AND project.user_id <> ?
WHERE project.user_id = ?
OR shared.user_id = ?
";
$query = $mysqli->prepare($sql);
$query->bind_param("iii", $user_id, $user_id, $user_id);
$query->execute();
Alternate Table Structure
I suggest updating your table structure so that you have three tables (effectively: users, projects, and project_users). The project_user table then acts as a conduit between the two entities (users and projects). In this case storing the relationship between the two (i.e. owner vs shared with).
TABLE: user TABLE: project TABLE: project_user
id | email | | id | content | | id | user_id | project_id | role
---+-------------------- ---+------------------------------ ---+---------+------------+-----
1 | james#website.com | | 1 | Project for James | | 1 | 1 | 1 | 1
2 | hannah#website.com | | 2 | Some other project for James | | 2 | 1 | 2 | 1
3 | lucy#website.com | | 3 | Project for Hannah | | 3 | 2 | 3 | 1
| | | 4 | A new project for hannah | | 4 | 2 | 4 | 1
| | | 5 | Hannah's pride and Joy | | 5 | 2 | 5 | 1
| | | 6 | Lucy cracking down | | 6 | 3 | 6 | 1
| | | 7 | Lucy's second project | | 7 | 3 | 7 | 1
| | | 8 | Lucy's public stuff | | 8 | 3 | 8 | 1
| | | | | | 9 | 1 | 5 | 2
| | | | | | 10 | 3 | 5 | 2
| | | | | | 11 | 1 | 8 | 2
| | | | | | 12 | 2 | 8 | 2
SQL
Example: https://www.db-fiddle.com/f/imQZ6cvEEff4VgRQ4v22Qo/0
SELECT project.id, project_user.user_id, project_user.role, project.content
FROM project
JOIN project_user
ON project_user.project_id = project.id
WHERE project_user.user_id = ?;
PHP
$sql = "
SELECT project.id, project_user.user_id, project_user.role, project.content
FROM project
JOIN project_user
ON project_user.project_id = project.id
WHERE project_user.user_id = ?
";
$query = $mysqli->prepare($sql);
$query->bind_param("i", $user_id);
$query->execute();
You can use another relationship between members and projects with a table like this :
CREATE TABLE `project_members` (
`project_id` INT NOT NULL,
`member_id` INT NOT NULL,
`is_owner` TINYINT NOT NULL DEFAULT 0,
PRIMARY KEY (`project_id`, `member_id`));
This table allows you to have many members linked to many projects.
The column is_owner is a boolean to easily see if the member is the owner or if the project has been shared to him.
Also it would be good to add foreign keys to project_id and member_id.
Imagine this is my table:
----------------------------------------------------
| id | user_id | amount_1 | amount_2 | amount_3 |
----------------------------------------------------
| 1 | 1 | 2 | 3 | 4 |
----------------------------------------------------
| 2 | 2 | 2 | 1 | 1 |
----------------------------------------------------
| 3 | 2 | 1 | 2 | 2 |
----------------------------------------------------
| 4 | 3 | 2 | 1 | 4 |
----------------------------------------------------
I need a query that gives me one result set for every entry that belongs to my current user, and then returns everything else as a single combined row with the amounts summed.
So in this case if I am user 1, I should get the following rows back:
---------------------------------------
| id | amount_1 | amount_2 | amount_3 |
---------------------------------------
| 1 | 2 | 3 | 4 | my own amounts
---------------------------------------
| 2 | 5 | 4 | 7 | everyone else's amounts
---------------------------------------
Any tips?
I've considered it might be a better idea to just filter the data in the code (php). Please help i'm starting to hate myself
You could use a UNION in sql
select 1 id, amount_1, amount_2, amount_3
from my_table
where user_id = 1
union
select 2 , sum(amount_1) , sum(amount_2), sum(amount_3 )
from my_table
where user_id <> 1
You can do with one query using union:
SELECT user_id, amount_1, amount_2, amount_3
FROM table
WHERE user_id = YOUR_USER_ID
UNION
SELECT -1, SUM(amount_1) AS amount_1, SUM(amount_2) AS amount_2, SUM(amount_3) AS amount_3
FROM table
WHERE user_id != YOUR_USER_ID
You can use aggregation in one fell swoop:
select (case when user_id = 1 then id end) as my_user_or_not,
sum(amount_1), sum(amount_2), sum(amount_3)
from t
group by my_user_or_not;
The null values in the first column indicate another user. You have labelled the column id, which is a bit problematic if you were -- for instance -- to choose user_id = 2 in your example. NULL seems safer for this purpose.
I wrote this sql query and it works but it is taking long time to be executed
SELECT trans_files.id, trans_files.game_name, trans_files.file_name,
COUNT(en_txt.id) as txt_num, COUNT(ar_txt.id) as n_txt_num
FROM trans_files
LEFT JOIN en_txt ON en_txt.file_id = trans_files.id
LEFT JOIN ar_txt ON ar_txt.en_text_id = en_txt.id
&& ar_txt.date = (SELECT MAX(ar_txt.date)
FROM ar_txt
WHERE en_text_id = en_txt.id)
GROUP BY trans_files.id
ORDER BY trans_files.id DESC, trans_files.game_name ASC, trans_files.file_name ASC
..
mysql> select * from ar_txt limit 3;
+----+------------+---------+----------------+---------------------+
| id | en_text_id | user_id | text | date |
+----+------------+---------+----------------+---------------------+
| 1 | 16 | 3 | Ϻ┘äÏ▓Ï╣┘è┘à | 2017-01-24 19:10:19 |
| 2 | 18 | 3 | Ϻ┘äϡϻϺϻ | 2017-01-24 19:13:36 |
| 3 | 3 | 3 | Ϻ┘äÏÀÏ¿┘èÏ¿┘è | 2017-01-24 19:15:48 |
+----+------------+---------+----------------+---------------------+
mysql> select * from en_txt limit 3;
+----+---------+------------------+
| id | file_id | text |
+----+---------+------------------+
| 1 | 2 | Apothecary Cheng |
| 2 | 2 | Blacksmith Ho Li |
| 3 | 2 | Apothecary Sung |
+----+---------+------------------+
mysql> select * from trans_files limit 3;
+----+-----------+-----------+
| id | game_name | file_name |
+----+-----------+-----------+
| 1 | drb | skills |
| 2 | drb | npcs |
| 3 | drb | test |
+----+-----------+-----------+
in this case i am using PDO and query to execute it
PHP code
so why does it take a long time?
Currently we are using a custom CI library to generate PDF files from documents which exist as database records in our database.
Each document is related to the contents (== rows) with a one-has-many relation. Each row has a number (field: row_span) to indicate how many lines it will use once it gets printed in the PDF.
Per PDF page that gets build, Rows needed for that page only are selected using a subquery:
$where = $this->docType."_id = ".$this->data['doc']->id." AND visible = 1";
$sql = "SELECT *,
(SELECT
sum(row_span) FROM app_".$this->docType."_rows X
WHERE X.position <= O.position
AND ".$where."
ORDER BY position ASC) 'span_total'
FROM app_".$this->docType."_rows O
WHERE ".$where."
HAVING span_total > ".(($i-1)*$this->maxRows)." AND span_total <= ".($i*$this->maxRows)." ORDER BY O.position ASC ";
$rows = $rows->query($sql);
In the code $i is the page number and $this->maxRows is loaded from the document template record which indicates how many available lines the PDF template has.
So when the SQL renders it might look like this for page 1 of an order with ID 834:
SELECT `app_order_rows`.*,
(SELECT SUM(`app_order_rows_subquery`.`row_span`) AS row_span
FROM `app_order_rows` `app_order_rows_subquery`
WHERE `app_order_rows_subquery`.`position` <= 'app_order_rows.position'
AND `app_order_rows_subquery`.`order_id` = 834
AND `app_order_rows_subquery`.`visible` = 1
ORDER BY `app_order_rows_subquery`.`position` asc) AS span_total
FROM (`app_order_rows`)
WHERE `app_order_rows`.`order_id` = 834
AND `app_order_rows`.`visible` = 1
HAVING span_total > 0
AND span_total <= 45
ORDER BY `app_order_rows`.`position` asc
And running this with EXPLAIN gives this as output:
+====+=============+=========================+======+===============+======+=========+======+======+=============================+===+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | |
+====+=============+=========================+======+===============+======+=========+======+======+=============================+===+
| 1 | PRIMARY | app_order_rows | ALL | NULL | NULL | NULL | NULL | 1809 | Using where; Using filesort | 1 |
+----+-------------+-------------------------+------+---------------+------+---------+------+------+-----------------------------+---+
| 2 | SUBQUERY | app_order_rows_subquery | ALL | NULL | NULL | NULL | NULL | 1809 | Using where | 2 |
+====+=============+=========================+======+===============+======+=========+======+======+=============================+===+
This is working great, but... When we have large orders or invoices it renders the documents very slow. This might be due to the subquery.
Does anyone have an idea on how to do the same select without subquery? Maybe we will have to go for a whole new approach to select rows and build the PDF. We are open for suggestions ^^
Thanks in advance
------------------------------- edit ------------------------------
The EXPLAIN after index creation:
+====+=============+=========================+=======+===============+============+=========+=======+======+=============+===+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | |
+====+=============+=========================+=======+===============+============+=========+=======+======+=============+===+
| 1 | PRIMARY | app_order_rows | ref | index_main | index_main | 5 | const | 9 | Using where | 1 |
+----+-------------+-------------------------+-------+---------------+------------+---------+-------+------+-------------+---+
| 2 | SUBQUERY | app_order_rows_subquery | range | index_main | index_main | 10 | NULL | 1 | Using where | 2 |
+====+=============+=========================+=======+===============+============+=========+=======+======+=============+===+
As you confirmed in the comments, the tables have no indexes.
The immediate solution would be:
create index index_main on app_order_rows (order_id, position);
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.