I have a database (mysql) table like
id | party | rights | m_id
---+---------+------------+---------
9 |abc | 3,5,6 | ["12,","15,"6"]
20 |xyz | 5,2 | ["6,","2,"9","12"]
21 |xyz 1 | 5,2 | ["6,","9,"12"]
Now I want to make my table in this way
search result for rights 5 is ["12,","15,"6"] ["6,","2,"12"] ["6,","9,"12"]
12 | abc , xyz,xyz1 |
15 | abc|
6 | abc , xyz,xyz1 |
9 | xyz,xyz1 |
Let's start with what I believe you already have. This is an sscce. If you adjust the mysql credentials it should run on your system, creating only a temporary MySQL table. It uses PDO to access the MySQL server. Which API you actually use is not important (i.e. as long as the other API is mysqli, because the mysql_* functions are depreacted ;-))
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'localonly', 'localonly', array(
PDO::ATTR_EMULATE_PREPARES=>false,
PDO::MYSQL_ATTR_DIRECT_QUERY=>false,
PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION
));
setup($pdo);
$statement = $pdo->prepare('
SELECT
*
FROM
soFoo
WHERE
FIND_IN_SET(:right, rights)
');
$statement->execute( array(':right'=>5) );
/* in one way or another you have a loop where you fetch the records
having '5' in the `rights` field
*/
foreach( $statement as $row ) {
echo $row['party'], ' ', $row['show_ids'], "\r\n";
}
function setup($pdo) {
$pdo->exec('
CREATE TEMPORARY TABLE soFoo
(`id` int, `party` varchar(9), `exc` varchar(13), `rights` varchar(5), `show_ids` varchar(27))
');
$pdo->exec( <<< eos
INSERT INTO soFoo
(`id`, `party`, `exc`, `rights`, `show_ids`)
VALUES
(9, 'Percept', 'Non-Exclusive', '3,5,6', '["12,","15,"6"]'),
(20, 'Tata Sky', 'Non-Exclusive', '5,4,', '["6,","9,"11"]'),
(21, 'Tata Sky', 'Exclusive', '5,4', '["6,","13","15,"2","4","9"]'),
(22, 'Simbiotic', 'Exclusive', '6,2', '["12,","15,"1","6","7","8"]')
eos
);
}
this prints
Percept ["12,","15,"6"]
Tata Sky ["6,","9,"11"]
Tata Sky ["6,","13","15,"2","4","9"]
and is (as I understand the question) as far as you've already got.
Now let's decode the JSON array and check whether it contains the element 9. If it does add inforamtion from the current row to an array called $parties
$parties = array();
/* in one way or another you have a loop where you fetch the records
having '5' in the `rights` field
*/
foreach( $statement as $row ) {
$ids = json_decode($row['show_ids'], true);
if ( in_array('9', $ids) ) {
$parties[$row['id']] = $row['party'];
}
}
var_export($parties);
prints
array (
20 => 'Tata Sky',
21 => 'Tata Sky',
)
But ... from a relational database point of view this is ....suboptimal.
The FIND_IN_SET clause hinders MySQL from using indices effectively; you're searching (compound) data within a single field. It's amazing what the database server implementations can do to improve performance; but it has limits.
And you're also transfering possibly unnecessary data from the MySQL server to the php instance (those records that have 5 in rights but not 9 in show_ids). If possible, this should be avoided. Networks/Network stacks are fast and can be optimized, RAM is cheap ...but again, there are limits.
So, I suggest you look into Database normalization on the one hand and/or document-oriented databases on the other hand.
I am going to give a example below so you can write in your way,
If you are having table below
id | name
1 | a,b,c
2 | b
and expected output like this
id | name
1 | a
1 | b
1 | c
2 | b
Then follow below solutions
If you can create a numbers table, that contains numbers from 1 to the maximum fields to split, you could use a solution like this:
select
tablename.id,
SUBSTRING_INDEX(SUBSTRING_INDEX(tablename.name, ',', numbers.n), ',', -1) name
from
numbers inner join tablename
on CHAR_LENGTH(tablename.name)
-CHAR_LENGTH(REPLACE(tablename.name, ',', ''))>=numbers.n-1
order by
id, n
Please see fiddle here.
If you cannot create a table, then a solution can be this:
select
tablename.id,
SUBSTRING_INDEX(SUBSTRING_INDEX(tablename.name, ',', numbers.n), ',', -1) name
from
(select 1 n union all
select 2 union all select 3 union all
select 4 union all select 5) numbers INNER JOIN tablename
on CHAR_LENGTH(tablename.name)
-CHAR_LENGTH(REPLACE(tablename.name, ',', ''))>=numbers.n-1
order by
id, n
an example fiddle is here.
Related
I have a products table which contains all my products. Those products table gets filled permanently with new products. However, I want to have the possibility to "hold up"/"pin" certain products to a place in the returned query collection.
Means, I want to set something like rank_index which contains the number the product should have in the returned query collection.
Example:
id title rank_index
1 An awesome product
2 Another product 5
3 Baby car
4 Green carpet 2
5 Toy
Lets assume the default order would be the id. But because the rank_index is set for the product with the id 4 I would like to get the collection with the following order of ids returned: 1, 4, 3, 5, 2.
Is this somehow possible to do? The rank_index column was just an idea of mine. I mean.. I also could do this on the php side and do a normal query which does only include the products without an rank_index and one which only contains products with an index_rank and order them manually on the php side.
However, because this takes a lot of time and processing power I am looking for a solution which is done by the database... Any ideas?
Btw: I am using Laravel 8 if this makes any difference.
Kind regards
This is a very tricky problem. If you try the other approach setting consecutive values -- like 2 and 3 -- you will see that they do not work.
There may be simpler ways to solve this. But, here is a brute force approach.
It constructs a derived table by enumerating the rows in the original table.
It adds into this table (using a left join) all the force-ranked values.
It joins in the rest of the values by enumerating the empty slots both in table1 and in the derived table.
So:
with recursive n as (
select row_number() over (order by id) as n
from table1 t1
),
nid as (
select n.n, t1.id
from n left join
table1 t1
on t1.rank_index = n.n
),
nids as (
select n.n, coalesce(n.id, t1.id) as id
from (select nid.*, sum(nid.id is null) over (order by nid.n) as seqnum
from nid
) n left join
(select t1.*, row_number() over (order by id) as seqnum
from table1 t1
where rank_index is null
) t1
on n.seqnum = t1.seqnum
)
select t1.*
from nids join
table1 t1
on t1.id = nids.id
order by nids.n;
Use the rank_index if it's not null as the ordering, id otherwise:
Since you want the rank_index to be ahead of an id, a -0.5 adjustment is made:
SELECT *
FROM table
ORDER BY IF(rank_index IS NULL, id, rank_index - 0.5)
You can use IF clause and to have the correct the number to get te right order, so
CREATE TABLE table1 (
`id` INTEGER,
`title` VARCHAR(18),
`rank_index` INT
);
INSERT INTO table1
(`id`, `title`, `rank_index`)
VALUES
('1', 'An awesome product', NULL),
('2', 'Another product', '5'),
('3', 'Baby car', NULL),
('4', 'Green carpet', '2'),
('5', 'Toy', NULL);
SELECT *
FROM table1
ORDER BY IF(rank_index IS NULL, id, rank_index + .01)
+----+--------------------+------------+
| id | title | rank_index |
+----+--------------------+------------+
| 1 | An awesome product | NULL |
| 4 | Green carpet | 2 |
| 3 | Baby car | NULL |
| 5 | Toy | NULL |
| 2 | Another product | 5 |
+----+--------------------+------------+
db<>fiddle here
Using GROUP_CONCAT() usually invokes the group-by logic and creates temporary tables, which are usually a big negative for performance. Sometimes you can add the right index to avoid the temp table in a group-by query, but not in every case.
https://stackoverflow.com/a/26225148/9685125
After Reading this post, I realized that I was doing wrong, because many time I made complicated query using huge GROUP_CONCAT(). Such as
GROUP_CONCAT(DISTINCT exam.title) AS exam,
GROUP_CONCAT(subject.title, '<br/> Th - ', mark.th, ' | PR - ', mark.pr SEPARATOR ',') AS mark
But what can be alternative of GROUP_CONCAT in following situation without using subquery. I mean using only Mysql join,
For example, let see two relational database and and query to explain my problem
Student
id | Rid | name
========================
1 | 1 | john
Marks
id | std_id | th
======================
1 | 1 | 60
2 | 1 | 70
3 | 1 | 80
4 | 1 | 90
"SELECT
student.en_name, mark.th
FROM student
JOIN mark ON student.id = mark.std_id
WHERE student.id=:id;"
Column would be repeated if only use JOIN
John: 60, John: 70, John: 80, John: 90
So, I use GROUP BY. But if I assign GROUP BY to student.id, only first row is fetched
"SELECT
student.en_name, mark.th
FROM student
JOIN mark ON student.id = mark.std_id
WHERE student.id=:id
GROUP BY student.id;"
Result is
John: 60
So to get result, we have to assign that group.concat
"SELECT
student.en_name,
GROUP_CONCAT(mark.th) as mark
FROM student
JOIN mark ON student.id = mark.std_id
WHERE student.id=:id
GROUP BY student.id;"
And final and expected result using exploding array
$name=$row['en_name'];
echo "$name: <br/>";
$mrk_array = explode(',',$row['mark']);
foreach($mrk_array as $mark){
echo $mark.", ";
}
John:
60, 70, 80, 90,
Here, I don't see any alternative of GROUP_CONCAT to fetch all associated value of each Id and prevent duplicate, please help me how to replace GROUP_CONCAT from here.
Also, one friend told me
So why GROUP_CONCAT if you're "exploding" it. You might as well return a nice associative array and then deal with displaying it there.
But I can't understand, what he means ?
Too long for a comment...
With your original query, you are effectively returning an array of rows (associative arrays):
array(array('en_name' => 'John', 'mark' => 60),
array('en_name' => 'John', 'mark' => 70),
array('en_name' => 'John', 'mark' => 80),
array('en_name' => 'John', 'mark' => 90)
)
When you use GROUP BY and GROUP CONCAT, you are effectively imploding the 'mark' elements of that array to
array('en_name => 'John', 'mark' => '60,70,80,90')
and you then have to explode the array again to process the data.
If you stick with your original query, you can instead do the imploding in your application framework e.g.
$name = "";
while ($row = $result->fetch()) {
if ($row['en_name'] != $name) {
$name = $row['en_name'];
echo "$name: <br/>" . $row['mark'];
}
else {
echo ',' . $row['mark'];
}
}
Output:
John:
60,70,80,90
This will generally be a lot faster than using GROUP BY and GROUP_CONCAT in the database.
Here is my scenario:
Database Name: Children
+-------------+---------+---------+
| child_id | name | user_id |
+-------------+---------+---------+
1 Beyonce 33
2 Cher 33
3 Madonna 33
4 Eminem 33
Database Name: Parents
+-------------+---------+---------+
| parent_id | child_id | parent_name |
+-------------+---------+---------+
1 1 Obama
2 1 Michelle
3 4 50cents
4 4 Gaga
Desired Output:
+-------------+---------+---------+
| child_id | name | parent Name |
+-------------+---------+---------+
1 Beyonce Obama (Row 1) Michelle (Row 2)
PHP SQL Query in PDO:
$sql = "SELECT Children.child_id, Children.name, Parents.parent_name
FROM Children
LEFT JOIN Parents
ON Children.child_id = Parents.child_id
WHERE Children.user_id = ?
";
$stmt = $db_PDO->prepare($sql);
if($stmt->execute(array($userId))) // $userId defined earlier
{
// Loop through the returned results
$i = 0;
foreach ($stmt as $row) {
$fetchArray[$i] = array (
'childId' => $row['child_id'],
'childName' => $row['name'],
'parentName' => $row['parent_name'],
// How do I save the multiple parents from other rows here ????
);
$i++;
}
}
How can I run a query that Joins 1 row to multiple rows in second table in PDO? I have read other topics here but I am unsure. Is it easier to add a second query that gets the linked parents for each child_id separately in a loop? I am worried that will be too much query. Can someone help me solve this?
Well, took me some fiddling to test it all out but here you go.
Unfortunately one cannot easely pivot tables in mysql but there are alternatives.
http://sqlfiddle.com/#!9/1228f/26
SELECT GROUP_CONCAT(
CONCAT_WS(':', Parents.parent_id,Parents.parent_name) ) FROM Parents where Parents.child_id=1
;
SELECT
Children.child_id,
Children.name,
GROUP_CONCAT(
CONCAT_WS(':', Parents.parent_id,Parents.parent_name) ) as parents
FROM
Children
LEFT JOIN Parents
ON Children.child_id = Parents.child_id
WHERE Children.user_id = 33
Group by Children.child_id
This query uses the group concat to concatenate all resulsts we want into a colon seperated string with the values we want, and comma's between the individual fields.
We could do some tricky magic to make them individual fields but that would break our php because we wouldnt know how much fields each query would return(adopted, orphan, no known parents, etc...)
In php you could feed them into an object
$parents = array();
$loop1 = explode(',',$row['parents']);
foreach($loop1 as $parentset) {
$parentdetail = explode(":",$parentset);// decide yourself how much detail you want in here... I jsut went with name and id.
$parent = new stdClass();
$parent->id = $parentdetail[0];
$parent->name = $parentdetail[1];
array_push($parents,$parent);
}
var_dump($parents);
Execute the below query . You will get the output as required, i just used the group by which will group the records as per the selected column
select a.child_id, name ,group_concat(parent_name) from children a, parents b where a.child_id =b.child_id group by a.child_id
HI this query works only if you are passing child id ,
select a.child_id, name ,group_concat(parent_name ) parent_name from children a, parents b where a.child_id =b.child_id and a.child_id=1
here i am using a function called group_concat which is used for concatinating the rows.It automatically takes the rows whose count is greater than 1.So no need of the extra code again
Basically, I have a mysql db table which contains a datetime column and a category column. I want to create a SQL query to retrieve all the values present in the category column and count how many occurences of each category values grouped by month/year of the datetime column. If it is possible, I'd also like totals to be returned. A total for the number of all occurences in a month and a total of category counted.
Note: the category values cannot be hardcoded because they are set by the user and stored in another table.
DB table has following structure:
datetime | category
2009-01-05 | fish
2009-01-06 | fish
2009-01-06 | potato
2009-01-16 | fish
2009-02-08 | pineapple
2009-02-15 | potato
I wish returned result from query would be:
Month | fish | potato | pineapple | total
2009-01 | 3 | 1 | 0 | 4
2009-02 | 0 | 1 | 1 | 2
Total | 3 | 2 | 1 | 6
I think (hope) it can be done in a single SQL query but I can't figure out how.
Can anyone help me?
Thanks!
Let me first say that I think this feels more like an issue to handle in your presentation logic (php code). However, SQL can produce such a result. You are trying to accomplish two different things.
First, you're looking for a PIVOT table. MySQL does not support the PIVOT command, but you can simulate it with MAX and CASE. This works well when you know the number of potential categories, but won't work in your case.
Next, you want to have row totals and then a final total row. Again, this is more appropriate to handle in the presentation layer.
However, using Dynamic SQL, you can achieve both a PIVOT table and row totals. Here is some sample code:
First build your PIVOT variable #sql:
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'COUNT(IF(category = ''', category, ''',1,NULL)) AS ', category)
) INTO #sql
FROM (
SELECT *,
#rn:=IF(#prevMonthYear=CONCAT(YEAR(datetime),'-',MONTH(datetime)),#rn+1,1) rn,
#prevMonthYear:=CONCAT(YEAR(datetime),'-',MONTH(datetime)) dt
FROM yourtable JOIN (SELECT #rn:=0,#prevParent:=0) t
) t
;
Now build your Row Summary variable #totsql:
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'SUM(', category, ') AS sum_', category)
) INTO #totsql
FROM (
SELECT *,
#rn:=IF(#prevMonthYear=CONCAT(YEAR(datetime),'-',MONTH(datetime)),#rn+1,1) rn,
#prevMonthYear:=CONCAT(YEAR(datetime),'-',MONTH(datetime)) dt
FROM yourtable JOIN (SELECT #rn:=0,#prevParent:=0) t
) t
;
Put it all together:
SET #sql = CONCAT('SELECT dt,
', #sql, ', COUNT(1) total
FROM (
SELECT *,
#rn:=IF(#prevMonthYear=CONCAT(YEAR(datetime),''-'',MONTH(datetime)),#rn+1,1) rn,
#prevMonthYear:=CONCAT(YEAR(datetime),''-'',MONTH(datetime)) dt
FROM yourtable JOIN (SELECT #rn:=0,#prevParent:=0) t
) t
GROUP BY dt
UNION
SELECT ''Totals'',', #totsql, ', SUM(total)
FROM (
SELECT dt,
', #sql, ', COUNT(1) total
FROM (
SELECT *,
#rn:=IF(#prevMonthYear=CONCAT(YEAR(datetime),''-'',MONTH(datetime)),#rn+1,1) rn,
#prevMonthYear:=CONCAT(YEAR(datetime),''-'',MONTH(datetime)) dt
FROM yourtable JOIN (SELECT #rn:=0,#prevParent:=0) t
) t
GROUP BY dt
) t2
;');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SQL Fiddle Demo
Results:
MONTH FISH POTATO PINEAPPLE TOTAL
2009-1 3 1 0 4
2009-2 0 1 1 2
Totals 3 2 1 6
You can have multiple nested queries or you can use mysql loops in this case. Easiest thing would be to get all the data you need and process it using php.
I have 2 joined tables, each one has a primary key column named id.
SELECT t1.*, t2.* from t1 join t2 on t1.fk_id=t2.id
When I run the query above, both id fields are selected (t1.id and t2.id). My question is, how can I select the correct ID while I am looping through the result set? If I select $result->id, I will get the t2.id. Is there any way that I can get the t1.id also without explicitly selecting it in the query (i.e. t1.id as t1_id?) Also, please, let us know about some of your practices when it comes to naming the primary key columns.
Thanks!
SELECT t1.id as id1, t2.id as id2, t1.*, t2.* from t1 join t2 on t1.fk_id=t2.id
You are probably using mysqli_result::fetch_assoc to return each row of your result set as an associative array. MySQL will let you have two columns with the same name in a query, but these do not map to an associative array the way you want them to—even though the associative array is doing exactly as it should.
Assume two tables, book and author, linked by the junction table book_author. In MySQL, you can run the following query, which returns two id columns:
SELECT b.*, a.*
FROM book AS b
JOIN book_author AS ba ON ba.book_id = b.id
JOIN author AS a ON a.id = ba.author_id
LIMIT 2;
+----+-----------------+----+--------------+
| id | title | id | name |
+----+-----------------+----+--------------+
| 1 | Design Patterns | 1 | Erich Gamma |
| 1 | Design Patterns | 2 | Richard Helm |
+----+-----------------+----+--------------+
If you try to map one of these rows to an associative array, you end up with a single id element in your array:
$row = $result->fetch_assoc();
print_r($row);
Array
(
[id] => 1
[title] => Design Patterns
[name] => Erich Gamma
)
The last id column in the row will overwrite any that precede it. Here’s the second row from the result set:
Array
(
[id] => 2
[title] => Design Patterns
[name] => Richard Helm
)
This is just the same as modifying the value of an element in an associative array;
$row = array();
$row['id'] = 1;
print_r($row);
Array
(
[id] => 1
)
$row['id'] = 2;
print_r($row);
Array
(
[id] => 2
)
If you give each column a unique name in your query, either by doing so in the table itself, or giving it an alias in the query, the problem is avoided:
SELECT b.id AS book_id, b.title,
a.id AS author_id, a.name
FROM book AS b
JOIN book_author AS ba ON ba.book_id = b.id
JOIN author AS a ON a.id = ba.author_id
LIMIT 2;
+---------+-----------------+-----------+--------------+
| book_id | title | author_id | name |
+---------+-----------------+-----------+--------------+
| 1 | Design Patterns | 1 | Erich Gamma |
| 1 | Design Patterns | 2 | Richard Helm |
+---------+-----------------+-----------+--------------+
$row = $result->fetch_assoc();
print_r($row);
Array
(
[book_id] => 1
[title] => Design Patterns
[author_id] => 1
[name] => Erich Gamma
)
Alternatively, you could (and almost certainly should) use prepared statements instead. Although this can get round the problem of duplicate column names, using unique column names in your queries still makes things much easier to read and debug:
$sql = 'SELECT b.*, a.* ' .
'FROM book AS b ' .
'JOIN book_author AS ba ' .
'ON ba.book_id = b.id ' .
'JOIN author AS a ' .
'ON a.id = ba.author_id';
$stmt = $mysqli->prepare($sql);
$stmt->execute();
$stmt->bind_result($book_id, $book_title, $author_id, $author_name);
while ($stmt->fetch()) {
printf("%s, %s, %s, %s\n",
$book_id,
$book_title,
$author_id,
$author_name);
}
You'll often see the primary key for table XXX named xxx_id. This keeps the name of the same "information identifier" the same everywhere: for example in another table YYY, you'll have YYY.xxx_id with a foreign key constraint to XXX.xxx_id. This makes joins easier (you don't have to specify the "on" constraint at all in many databases) and it solves the problem you're running into as well.
I'm not saying you should prefix every column name to create a faux-namespace, but in the case of "id" it is actually useful and descriptive. It is, after all, not just any kind of ID, it's a user ID, site ID, game ID, contact ID, what have you.