I'm designing a search engine which will sift through data in a database. I have 2 tables in my database:
Members:= `MemberID|Firstname|Secondname|Member_Type|Skills|Marital Status`
Schedule:= `ScheduleID|MemberID (foreign key)|six_am|seven_am|...|nine_pm`
(where the values for six_am|seven_am|...|nine_pm are boolean values and represent availablity)
My question is if there's a way to create a single SQL query similar to:
SELECT * FROM members1 (WHERE $x LIKE $firstname OR $x LIKE SecondName...) INNER JOIN schedules ON (members1.members_id = schedules.member_id)
WHERE eleven_am=1 OR twelve_pm=1
This way, I can enter a single query instead of multiple ones.
You can use prepared statements:
SET #query = 'build your own query in any way';
PREPARE stmt FROM #query;
SET #id = 4; -- here you can add variable for safe inserting to the query. if you want to
EXECUTE prepared_query USING #id;
DEALLOCATE PREPARE stmt;
The #query can be built by some server side language (PHP for example) or by string manipulations in pure SQL.
Afterwards you send the "template" to execution.
If you'll provide more details I can build more exact example.
But this is an idea.
Related
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.
Alright so I'll try to explain it as simple as possible; consider that I have two database tables (MySQL Server / MariaDB, database-related tasks coded in procedural style in PHP using prepared statements):
in one, I have a column of datatype JSON, whose content corresponds to sth like {name1:info,name2:info}
In another one, I have simple non-json records, having a structure like:
name | status
------+--------
name1 | statusX
------+--------
name2 | statusY
My Goal: I need to retrieve the name2 from table 1), but I also need to retrieve the status of the person having that same name (which in this case is statusY). Note that, for the retrieval of name2, I cannot rely on indexes of the json object (name2 may be the first key of the json object).
How I would do it so far:
A) Get the name2 from table 1) in a first query, sanitize it, and
B) use it in the second query which then correctly retrieves the statusY
Both statements A) and B) are parametrized prepared sql statements, triggered by an AJAX Call at a regular interval (AJAX Polling).
Given that these database queries are thus executed frequently, I want them to be executed as fast as possible, and thus ideally reduce my two queries above to a single one. My problem: I need the result of statement A) to execute statement B), so I cannot summarize the two queries into a single prepared statement, as prepared statements cannot contain multiple sql statements. The best solution to reach what I want is create a stored procedure like:
SET #name = SELECT ..... FROM table_1; SELECT .... FROM table_2;
And then execute it as parametrized prepared statement; is that correct?
I'm not at all experienced with stored procedures in MySQL Server, didn't really need them yet, but they seem to be the only solution if you want to wrap > 1 sql statements into a single prepared statement. Is this assumption, and thus the conclusion that I gotta create a stored procedure to reach what I want, correct?
IMPORTANT NOTE: I don't know the name I need to query. From the two names of the json column of table 1), I only know the other name. In other words, I have one name of a person X, and I want to get the status of all the persons which have been associated with that person X in table 1), while the status of each person is listed in table 2), to avoid to have duplicate status storage in the DB. ATM, I retrieve the other names of each relation record from DB 1) by using a conditional statement saying sth like
UPDATE
See added answer below, got it working.
You can query JSON data type with MySQL (if version > 5.7), and thus you can simply do everything with a single query
Give this a try
SELECT t1.name1, t1.name2, t2.status
FROM
(
SELECT JSON_EXTRACT(your_json_column, "$.name1") AS name1,
JSON_EXTRACT(your_json_column, "$.name2") AS name2
FROM table1
WHERE JSON_EXTRACT(your_json_column, "$.name1") = 'info'
) t1
INNER JOIN table2 t2 ON t2.`name`=t1.name2
Adapt the name of your_json_column. Also I assumed that you wanted to search the name2 of a specific name1, thus my WHERE clause, remove it if it was a false assumption.
Okay got it working, pretty much thanks to the solution proposed by Thomas G and some hints of JNevill (cheers guys!):
SELECT t1.info1, t1.info2, t1.info3, t1.other_name, t2.status FROM (
SELECT
field1 AS info1,
field2 AS info2,
field3 AS info3,
CASE
WHEN JSON_VALUE(JSON_KEYS(json_names_column),"$[0]") = 'name1'
THEN JSON_VALUE(JSON_KEYS(json_names_column),"$[1]")
ELSE JSON_VALUE(JSON_KEYS(json_names_column),"$[0]")
END
AS other_name
FROM table1
WHERE id = 345
) t1 INNER JOIN table2 t2 ON t1.other_name = t2.name;
Note that I used JSON_VALUE(JSON_KEYS()) instead of JSON_EXTRACT, to only return the needed name as name data of t1, and because I don't know the name to retrieve before the query, so I cannot use the WHEREclause proposed by Thomas G.
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.
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.
I am trying to create a temporary table from the results of multiple tables that are alike (same table structure). After creating the temporary table and runing the subsequent queries, I would like to store the results from the temporary table in an array to be accessible later in the script/program. I have tried searching for an answer and can't seem to find one.
I have tried nextRowset() as well as separating the queries, but nothing seems to be working like I expect it to work.
Here is my code:
$pdo = new PDO("mysql:host=".$_SESSION['server'].";dbname=data".$_SESSION['sysident'],$user,$pass);
$stmt = $pdo->prepare("DROP TABLE IF EXISTS $tabletocreate;
CREATE TEMPORARY TABLE $tabletocreate LIKE table1;
INSERT INTO $tabletocreate (SELECT * FROM table1 WHERE (MISC LIKE '%:memno%' OR MEMNO = :memno)) UNION (SELECT * FROM table2 WHERE (MISC LIKE '%:memno%' OR MEMNO = :memno)) UNION (SELECT * FROM table3 WHERE (MISC LIKE '%:memno%' OR MEMNO = :memno)) ORDER BY SLIPNO;
SELECT * FROM $tabletocreate");
$stmt->bindParam(":memno",$_SESSION['memno']);
$stmt->execute();
$stmt->nextRowset();
$test = $stmt->fetchAll();
print_r($test);
I am unsure as to why the results are not being stored into the array. From what I can tell, everything seems right and no errors occur when the script is ran. I appreciate any help that anyone can offer.
UPDATE - I found out why the query wasn't working. I was using a "-" in the table name I was trying to create which isn't allowed.
You cannot run multiple queries in a single ->query() call. This is a security measure in the underlying PHP mysql drivers, to prevent some form of SQL injection attacks. Doesn't matter which DB interface library you're using, because they all use the same underlying drivers. You'll have to run each seperate query separately:
->query("DROP TABLE IF EXISTS ...");
->query("CREATE TEMPORARY TABLE ...");
->query("INSERT INTO ...");
etc...
I was trying to create a table name with a "-" in the table name. After removing this from the table name, all the queries executed successfully and my PHP code worked as intended.