MySQL - tranpose one row to columns dynamically - php

Hello supposed to have a temporary table that having resultset like this:
And the month column is changing dynamically based on user input. Now I want my month column values (months) to become columns and under it was the amount and everything will be group by account_id.
I've searched every answers for this but I still can't get it. Here's my code but it is not returning accurately and I'm still lost here:
CREATE DEFINER=`root`#`localhost` PROCEDURE `test`()
BEGIN
SET group_concat_max_len=2048;
SELECT CONCAT('
SELECT account_id, month ',
GROUP_CONCAT('
GROUP_CONCAT(IF(amount=',QUOTE(amount),',value,NULL))
AS `',REPLACE(amount,'`','``'),'`'
), '
FROM tmp_results
GROUP BY account_id
')
INTO #sql
FROM (
SELECT account_id, month FROM tmp_results
) t;
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
Can someone help me step by step that I could understand it? PHP laravel step by step also are welcome. I'm a newbie developer. Thank you!

Related

Prepared statements against a JSON key throwing "must appear in the GROUP BY clause" in PostgreSQL using PDO

I am trying to dynamically group by json keys to find the average answer.
select
tbl.data->>'Example' as response,
count(tbl.data->>'Example') as total
from table tbl
group by tbl.data->>'Example'
order by total
limit 1
This Query works fine when it is ran inside PostgreSQL and I get my expected result:
| response | total |
|--------------------------|
| Hello World | 4 |
The issue now occurs when I don't know the keys. They're dynamically created and thus I need to loop over them.
$sql = <<<END
select
tbl.data->>? as response,
count(tbl.data->>?) as total
from table tbl
group by tbl.data->>?
order by total
limit 1
END;
$stmt = (new \PDO(...))->Prepare($sql);
$stmt->execute(array_fill(1, 3, 'Example'));
$stmt->fetch(\PDO::FETCH_ASSOC);
'Example' comes from user input. The JSON is created by the user, the keys could be anything. In this case, its hard-coded but I run a seperate SQL query to get all the keys and loop over them:
But I always get the following error:
tbl.data must appear in the GROUP BY clause or be used in an aggregate function
Now, I assume this is because of the prepared statement treating the column as data but this information derives from user input so I need to use prepared statements.
select json_object_keys(data) as keys from table
Any guess to how I can resolve this?
I don't know Php. But from this link(https://kb.objectrocket.com/postgresql/postgres-stored-procedure-call-in-php-1475), seems it's pretty ok to use functions in php.
demo
CREATE OR REPLACE FUNCTION find_answer (_key text)
RETURNS json
AS $$
DECLARE
is_exists boolean;
_sql text;
_return json;
BEGIN
_sql := $sql$
SELECT
row_to_json(cte.*)
FROM (
SELECT
tbl.data ->> $1 AS response,
count(tbl.data ->> $1) AS total
FROM
tbl
GROUP BY
1
ORDER BY
total DESC
LIMIT 1) cte $sql$;
SELECT
(data[_key] IS NULL) INTO is_exists
FROM
tbl;
IF is_exists THEN
RAISE EXCEPTION '% not exists.', _key;
ELSE
RAISE NOTICE '% sql', _sql;
EXECUTE _sql
USING _key INTO _return;
RETURN _return;
END IF;
END
$$
LANGUAGE plpgsql;
then call it. select * from find_answer('example');
about Prepared statements
Prepared statements only last for the duration of the current
database session. When the session ends, the prepared statement is
forgotten, so it must be recreated before being used again. This also
means that a single prepared statement cannot be used by multiple
simultaneous database clients; however, each client can create their
own prepared statement to use. Prepared statements can be manually
cleaned up using the DEALLOCATE command.

Prepared statement - cross table update

I am attempting to use a prepared statement in combination with a cross table update. I have prepared a sample script that is representative of our larger database. This first section does what I want without a prepared statement, but I am hoping to avoid copy/pasting this for every column of my data.
SET SESSION group_concat_max_len = 1000000000;
drop table if exists update_test;
create table update_test(
time_index decimal(12,4),
a varchar(20),
b varchar(20),
c varchar(20));
insert into update_test(time_index) values(20150101.0000),(20150101.0015),(20150101.0030);
drop table if exists energy_values;
create table energy_values(
time_stamp decimal(12,4),
site_id varchar(5),
energy int);
insert into energy_values
values(20150101.0000,'a',100),(20150101.0000,'b',200),(20150101.0000,'c',300),
(20150101.0015,'a',400),(20150101.0015,'b',500),(20150101.0015,'c',600),
(20150101.0030,'a',700),(20150101.0030,'b',800),(20150101.0030,'c',900);
drop table if exists update_test_sites;
create table update_Test_sites(
sites varchar(5));
insert into update_test_sites values
('a'),('b'),('c');
update update_test, energy_values, update_test_sites
set update_test.a=energy_values.energy
where update_test.time_index = energy_values.time_stamp
and energy_values.site_id ='a';
update update_test, energy_values, update_test_sites
set update_test.b=energy_values.energy
where update_test.time_index = energy_values.time_stamp
and energy_values.site_id ='b';
update update_test, energy_values, update_test_sites
set update_test.c=energy_values.energy
where update_test.time_index = energy_values.time_stamp
and energy_values.site_id ='c';
select * from update_test;
Which is why I have attempted something like this as a replacement for the update functions. However, I often get a syntax error report. Can anyone identify where I am going wrong? It would be much appreciated!
SELECT
concat(
'update update_test, energy_values, update_test_sites
set update_test.',sites,'=energy_values.energy
where update_test.time_index = energy_values.time_stamp
and energy_values.site_id = ',sites,';
select * from update_test;')
from update_test_sites
where sites = 'a'
INTO #sql;
PREPARE stmt FROM #sql;
EXECUTE stmt;
I've never seen "SELECT INTO" work that way. In my experience, it is used like so:
SELECT [field_list] INTO [variable_list]
FROM [some_table]
[etc...]
I don't think it can be used to store a resultset like it appears you are attempting.
With some tweaking and doing this in a stored procedure, you could use a cursor to iterate over the results to prepare and execute each generated statement individually.

MySQL Stored Procedure Creation with Input and Output Parameters

Answer: https://stackoverflow.com/a/24034862/1173155
I am testing a Stored procedure as follows:
DELIMITER $$
CREATE PROCEDURE `test1`(IN `tab_name` VARCHAR(40), IN `value_of` VARCHAR(40), OUT `the_id` INT(1))
BEGIN
SET #t1=CONCAT('
BEGIN
IF EXISTS (SELECT ',tab_name,'.id from ',tab_name,' where ',tab_name,'.',tab_name,' = ',value_of,')
THEN
select id into ',the_id,' from ',tab_name,' where ',tab_name,'.',tab_name,' = ',value_of,';
ELSE
insert into ',tab_name,' values (NULL,',value_of,');
END IF;');
PREPARE stmt3 FROM #t1;
EXECUTE stmt3;
DEALLOCATE PREPARE stmt3;
END $$
DELIMITER ;
Trying to select the id, or insert. I have a single row in the table, when I try and use this Procedure as is; it doesn't return anything.
The identifier for the value I want is the table name for simplicities sake. Have spent many hours and I'm at a loss.
Have tried everything, even now just thought adding the NULL, would work as the table has two values and I had forgotten to put it in only to realise I was trying it within the first if clause... IE, entering data to ensure that the first statement would be invoked
Thanks for any help
Update
Still having trouble with this. Trying to make it as simple as possible now.
I have a city table, with 1 record it in. Structure is like so city.city = 'Dublin'
Value and table name are the same. The follow select statement works when executed via phpMyAdmin
BEGIN
SET #t1= CONCAT("
SELECT id
FROM ",tab_name,"
WHERE ",tab_name," = '",value_of,"'
");
PREPARE stmt3 FROM #t1;
EXECUTE stmt3;
DEALLOCATE PREPARE stmt3;
END
Now I just need to ensure that if the select statement doesn't return anything, then insert the value and return the select statement.
This seems like such a simple problem, yet I can't find the solution anywhere
--
INSERT IGNORE is not good because it increments the ID regardless of inserting. Is there a work around for this because I could just use that then.
INSERT IGNORE... // Without increment?
SELECT ^ ....
Further Update
My procedure now looks like this (takes two VARCHAR parameters) and this is currently working. It doesn't increment the ID if the value already exists either which is simply fantastic.
BEGIN
SET #t1= CONCAT("INSERT INTO ",tab_name,"(",tab_name,")
SELECT '",city_name,"' FROM dual
WHERE NOT EXISTS (SELECT 1
FROM ",tab_name,"
WHERE ",tab_name," = '",city_name,"');
");
PREPARE stmt3 FROM #t1;
EXECUTE stmt3;
DEALLOCATE PREPARE stmt3;
END
All I want now is for a simple SELECT statement to return the id of the row with that city name. eg SELECT id FROM tab_name WHERE tab_name = 'city_name';
But adding this in causes an error :( Thanks if anyone has a solution to this ridiculous problem
The solution that is working via phpMyAdmin. Will have to test in PHP and PDO too and see if all is good.
CREATE PROCEDURE `select_insert`(IN `the_value` VARCHAR(150), IN `tab_name` VARCHAR(50))
BEGIN
SET #t1= CONCAT("INSERT INTO ",tab_name,"(",tab_name,")
SELECT '",the_value,"' FROM dual
WHERE NOT EXISTS (SELECT 1
FROM ",tab_name,"
WHERE ",tab_name," = '",the_value,"');
");
PREPARE stmt FROM #t1;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET #t2 = CONCAT("SELECT id FROM ",tab_name," WHERE ",tab_name," = '",the_value,"'");
PREPARE stmt1 FROM #t2;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
END
This is a generic procedure for inserting or retrieving values that are unique in the table. I used a city example while trying to figure this out.
The first prepared statement inserts the value provided into the table name provided. It doesn't increase the auto increment value if no insertion is made.
The second prepared statement retrieves the value. There shouldn't really be a case where no value is returned due to the nature of the first query.
The statement you wrote should not return results. Both the select into and insert statements do not return results.
It's a stored procedure.
Do the SELECT first, then follow it with the insert(s). The select will be the first output result set of the procedure.

selecting values dynamic from columns using pivoting mysql

I have a brands table and posts table.
Below is their schema
Brands :
brands_id, friendly, short ,followers, created, location, brandID,
handle, url, pic, utcOffset, posts, engagements, engagedUser,
etc(sufficient for current question)
Posts :
post_id, brandID, postID, handle, posted, content, postType, comments,
etc (sufficient for current question)
where postType= link, status,video,question,poll,etc
Now I have hardcoded pivoting with the following query:
select b.friendly,
sum(case when p.postType='link' then 1 else 0 end) as 'link',
sum(case when p.postType='video' then 1 else 0 end) as 'video',
sum(case when p.postType='status' then 1 else 0 end) as 'status',
sum(case when p.postType='photo' then 1 else 0 end) as 'photo',
count(p.postType)
from brands b, posts p
where b.handle
in ('chevroletcanada','dodgecanada')
and p.handle=b.handle
and date(p.posted)
BETWEEN "2013-06-02" and "2013-08-11"
group by b.friendly
But in the above query I have used types of postType statically, i.e for links, status, video, photo. Now if a new postType is added to the posts table, this won't work, as I would have to change the query too. Also if existing values in postType is deleted, then too the query have to be changed again.
My question is how can I achieve this dynamically so that when new postType values for instance tweet is added in posts table then tweet will show up in the result sets as well. And the same for deletion of any postType.
If you haven't understood question, please inform me.
I have read the below posts, but unable to figure out:
MySQL or PHP Turning rows into columns dynamically
MySQL pivot table query with dynamic columns
Thanks in advance!!
This is the solution for the above problem. Dynamic pivot can be achieved like this:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT(
'sum(case when postType = ''',
postType,
''' then 1 end) as `',
postType, '`')
) INTO #sql
FROM posts;
SET #sql = CONCAT('SELECT b.friendly, count(p.postType), ', #sql, ' from brands b, posts p where b.handle in ("dodgecanada","chevroletcanada") and p.handle=b.handle
and date(p.posted) BETWEEN "2004-08-11" and "2013-09-11" group by b.friendly');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
The above query displays right results in SQL interface HeidiSQL. But returns:
"#1243 - Unknown prepared statement handler (stmt) given to EXECUTE" message in phpMyAdmin.
Can anyone tell me why is this so? Is 'prepared' statement not supported in Mysql? or is there any issue in phpMyAdmin running prepared statements.
How can I overcome this in phpMyAdmin?And can anyone tell me how can I do this in PHP? Thanks!!

Mirror mysql table structure

How would someone maintain different tables that share a similar structure:
Example: I have 600 tables with 20 fields and I've been using this structure for months, what if I need to delete 1 field and add 2 new ones, how could it possibly be done just by changing a master table which contains the structure that must be used by all of the other cloned tables?
Well, you probably know that your structure is far from being optimal and the best solution is to reorganise it. It is not always easy with legacy systems though, so the task can be still performed with MySQL only.
You will need "cursors" there which can be only used inside stored procedures, so you'll need to create a stored procedure first (its sample code is below) and then to execute is as CALL alter_many_tables();
CREATE PROCEDURE alter_many_tables()
BEGIN
-- reading names of the table to update in a cursor
DECLARE tables_cursor CURSOR FOR
SELECT DISTINCT
`TABLE_NAME`
FROM
`information_schema`.`columns`
WHERE
`TABLE_NAME` LIKE '%\_modulep'
;
-- condition for the loop over found tables to stop
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
-- looping
read_loop: LOOP
-- reading table name into a variable
FETCH tables_cursor INTO table_name;
-- check if the loop is over
IF done THEN
LEAVE read_loop;
END IF;
-- forming a table update SQL (modify it as you need)
SET #sql = CONCAT('ALTER TABLE ', #table_name, ' ADD COLUMN `new_column` VARCHAR(45) NULL DEFAULT NULL');
-- executing the SQL we have composed above
PREPARE stmt FROM #sql;
EXECUTE stmt;
END LOOP;
-- closing the cursor
CLOSE table_cursor;
END;
There might be some minor syntax errors in the snippet above as I can't test at the moment, but you get the idea.

Categories