Related
I want to perform a query like to get the last record of any type in DB, at my localhost, I use Maria Db and the query is as follow:
SELECT *
FROM table_a
WHERE column_a=999
OR column_b=999
GROUP
BY `group`;
group is a column which I save type in it, for instance: marketing, blog, order, etc
This query works fine on local, but on the server I get the following error:
SQLSTATE[42000]:
Syntax error or access violation:
1055 Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column
'db_name.table_a.id' which is not functionally dependent on columns in GROUP BY clause;
this is incompatible with sql_mode=only_full_group_by\n
The SQL being executed was:
SELECT * FROM `table_a` WHERE (`column_a`=999) OR (`column_b`=999) GROUP BY `group`"
According to MySQL document I can use the following command to make this possible:
SET GLOBAL sql_mode=(SELECT REPLACE(##sql_mode,'ONLY_FULL_GROUP_BY',''));
But I don't have the sufficient privilege on Db and get the following error:
#1227 - Access denied; you need (at least one of) the SUPER privilege(s) for this operation
I asked the hosting to do this for me, they replied that they don't want to do this action
I use the YII2 framework, and now I want a way to add this on the option of database_config.php of the framework or change the query to something else with the same result, and not losing performance
ONLY_FULL_GROUP_BY is a good thing, which enforces basic ANSI SQL rules. Don't change it, fix your code instead.
From there one: you want entire records, so you should not think aggregation, but filtering.
Then: in a database table, records are unordered; for your question to just make sense, you need a column that defines the ordering of rows in each group, so it unambiguous what "last" record mean. Let me assume that you have such column, called id.
Here is a typical approach at this top-1-per-group problem, using a correlated subquery for filtering:
SELECT *
FROM table_a a
WHERE
999 IN (column_a, column_b)
AND id = (
SELECT MAX(a1.id)
FROM table_a a1
WHERE 999 IN (a1.column_a, a1.column_b) AND a1.grp = a.grp
)
Alternatively, if you are running MySQL 8.0, you can use window functions:
SELECT *
FROM (
SELECT a.*,
ROW_NUMBER() OVER(PARTITION BY grp ORDER BY id DESC) rn
FROM table_a a
WHERE 999 IN (column_a, column_b)
) a
WHERE rn = 1
Side note: group is a language keyword, hence a poor choice for a column name. I renamed it to grp in the queries.
There are a few ways to "bypass" the sql_mode but be aware that the result you get might not be correct.
First you can use ANY_VALUE(). Example like this:
SELECT any_value(column_a), any_value(column_b), `group` FROM table_a
WHERE (column_a=999) OR (column_b=999) GROUP BY `group`;
When using ANY_VALUE() function you have to write all the columns in SELECT from the table and append with ANY_VALUE() except for the column that you use in the GROUP BY.
Using MAX() or MIN() can return result but still it might not be the correct
result, especially for any row(s) that have more than 1 count:
SELECT MAX(column_a), MAX(column_b), `group`
FROM table_a
WHERE (column_a=999) OR (column_b=999) GROUP BY `group`;
Using GROUP_CONCAT will give you a view at what are the values in non-grouped columns. Compare the results with the other queries above and you can see on row(s) that returns more than one count, does the other queries returning according to what you want?
SELECT group_concat(column_a), group_concat(column_b), group_concat(`group`)
FROM table_a
WHERE (column_a=999) OR (column_b=999) GROUP BY `group`;
I'm not sure if you can do this but you can set the sql_mode off temporarily then you should be able to run your query:
SET sql_mode=""; -- you don't need to set global privilege.
SELECT * FROM table_a
WHERE (column_a=999) OR (column_b=999) GROUP BY `group`;
Demo here.
Still, the best option is to retain the sql_mode as it is and construct the query according to the requirement.
P/S: GROUP is a reserved word in both MySQL & MariaDB. You can use it as column name but you have to always add back-ticks to define the column or else, running the query will return you an error like
Query: select * from table_a group by group
Error Code: 1064
You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'group' at line 1
I am wondering if there is away (possibly a better way) to order by the order of the values in an IN() clause.
The problem is that I have 2 queries, one that gets all of the IDs and the second that retrieves all the information. The first creates the order of the IDs which I want the second to order by. The IDs are put in an IN() clause in the correct order.
So it'd be something like (extremely simplified):
SELECT id FROM table1 WHERE ... ORDER BY display_order, name
SELECT name, description, ... WHERE id IN ([id's from first])
The issue is that the second query does not return the results in the same order that the IDs are put into the IN() clause.
One solution I have found is to put all of the IDs into a temp table with an auto incrementing field which is then joined into the second query.
Is there a better option?
Note: As the first query is run "by the user" and the second is run in a background process, there is no way to combine the 2 into 1 query using sub queries.
I am using MySQL, but I'm thinking it might be useful to have it noted what options there are for other DBs as well.
Use MySQL's FIELD() function:
SELECT name, description, ...
FROM ...
WHERE id IN([ids, any order])
ORDER BY FIELD(id, [ids in order])
FIELD() will return the index of the first parameter that is equal to the first parameter (other than the first parameter itself).
FIELD('a', 'a', 'b', 'c')
will return 1
FIELD('a', 'c', 'b', 'a')
will return 3
This will do exactly what you want if you paste the ids into the IN() clause and the FIELD() function in the same order.
See following how to get sorted data.
SELECT ...
FROM ...
WHERE zip IN (91709,92886,92807,...,91356)
AND user.status=1
ORDER
BY provider.package_id DESC
, FIELD(zip,91709,92886,92807,...,91356)
LIMIT 10
Two solutions that spring to mind:
order by case id when 123 then 1 when 456 then 2 else null end asc
order by instr(','||id||',',',123,456,') asc
(instr() is from Oracle; maybe you have locate() or charindex() or something like that)
If you want to do arbitrary sorting on a query using values inputted by the query in MS SQL Server 2008+, it can be done by creating a table on the fly and doing a join like so (using nomenclature from OP).
SELECT table1.name, table1.description ...
FROM (VALUES (id1,1), (id2,2), (id3,3) ...) AS orderTbl(orderKey, orderIdx)
LEFT JOIN table1 ON orderTbl.orderKey=table1.id
ORDER BY orderTbl.orderIdx
If you replace the VALUES statement with something else that does the same thing, but in ANSI SQL, then this should work on any SQL database.
Note:
The second column in the created table (orderTbl.orderIdx) is necessary when querying record sets larger than 100 or so. I originally didn't have an orderIdx column, but found that with result sets larger than 100 I had to explicitly sort by that column; in SQL Server Express 2014 anyways.
SELECT ORDER_NO, DELIVERY_ADDRESS
from IFSAPP.PURCHASE_ORDER_TAB
where ORDER_NO in ('52000077','52000079','52000167','52000297','52000204','52000409','52000126')
ORDER BY instr('52000077,52000079,52000167,52000297,52000204,52000409,52000126',ORDER_NO)
worked really great
Ans to get sorted data.
SELECT ...
FROM ...
ORDER BY FIELD(user_id,5,3,2,...,50) LIMIT 10
The IN clause describes a set of values, and sets do not have order.
Your solution with a join and then ordering on the display_order column is the most nearly correct solution; anything else is probably a DBMS-specific hack (or is doing some stuff with the OLAP functions in standard SQL). Certainly, the join is the most nearly portable solution (though generating the data with the display_order values may be problematic). Note that you may need to select the ordering columns; that used to be a requirement in standard SQL, though I believe it was relaxed as a rule a while ago (maybe as long ago as SQL-92).
Use MySQL FIND_IN_SET function:
SELECT *
FROM table_name
WHERE id IN (..,..,..,..)
ORDER BY FIND_IN_SET (coloumn_name, .., .., ..);
For Oracle, John's solution using instr() function works. Here's slightly different solution that worked -
SELECT id
FROM table1
WHERE id IN (1, 20, 45, 60)
ORDER BY instr('1, 20, 45, 60', id)
I just tried to do this is MS SQL Server where we do not have FIELD():
SELECT table1.id
...
INNER JOIN
(VALUES (10,1),(3,2),(4,3),(5,4),(7,5),(8,6),(9,7),(2,8),(6,9),(5,10)
) AS X(id,sortorder)
ON X.id = table1.id
ORDER BY X.sortorder
Note that I am allowing duplication too.
Give this a shot:
SELECT name, description, ...
WHERE id IN
(SELECT id FROM table1 WHERE...)
ORDER BY
(SELECT display_order FROM table1 WHERE...),
(SELECT name FROM table1 WHERE...)
The WHEREs will probably take a little tweaking to get the correlated subqueries working properly, but the basic principle should be sound.
My first thought was to write a single query, but you said that was not possible because one is run by the user and the other is run in the background. How are you storing the list of ids to pass from the user to the background process? Why not put them in a temporary table with a column to signify the order.
So how about this:
The user interface bit runs and inserts values into a new table you create. It would insert the id, position and some sort of job number identifier)
The job number is passed to the background process (instead of all the ids)
The background process does a select from the table in step 1 and you join in to get the other information that you require. It uses the job number in the WHERE clause and orders by the position column.
The background process, when finished, deletes from the table based on the job identifier.
I think you should manage to store your data in a way that you will simply do a join and it will be perfect, so no hacks and complicated things going on.
I have for instance a "Recently played" list of track ids, on SQLite i simply do:
SELECT * FROM recently NATURAL JOIN tracks;
I'm a bit confused about DISTINCT keyword. Let's guess that this query will get all the records distincting the columns set in the query:
$query = "SELECT DISTINCT name FROM people";
Now, that query is fetching all the records distincting column "name" and at the same time only fetching "name" column. How I'm supposed to ONLY distinct one column and at the same time get all the desired columns?
This would be the scheme:
NEEDED COLUMNS
name
surname
age
DISTINCTING COLUMNS
name
What would be the correct sintaxis for that query? Thanks in advance.
If you want one row per name, then a normal method is an aggregation query:
select name, max(surname) as surname, max(age) as age
from t
group by name;
MySQL supports an extension of the group by, which allows you to write a query such as:
select t.*
from t
group by name;
I strongly recommend that you do not use this. It is non-standard and the values come from indeterminate matching rows. There is not even a guarantee that they come from the same row (although they typically do in practice).
Often, you want something like that biggest age. If so, you handle this differently:
select t.*
from t
where t.age = (select max(t2.age) from t t2 where t2.name = t.name);
Note: This doesn't use group by. And, it will return duplicates if there are multiple rows with the same age.
Another method uses variables -- another MySQL-specific feature. But, if you are still learning about select, you should probably wait to learn about variables.
I need to find the largest value from one particular column in a mysql table where the value of another column is equivalent to something. But, with the query that I'm using, I keep on getting a message that displays and SQL error. My query is aS follows:
SELECT MAX(message_id) AS top_message HAVING(child_id) = '".$message_id[$i]."'
Any suggestions?
You are also missing a table name:
SELECT MAX(message_id) AS top_message FROM tablename WHERE child_id = '".$message_id[$i]."'
You should use WHERE instead of HAVING Clause:
SELECT MAX(message_id) AS top_message
FROM tablename
WHERE child_id = '".$message_id[$i]."'
Use only HAVING clause when you have an aggregated conditon.
You need a from clause and a where clause. The having clause is used for group filters. You don't have a group by clause, so there is no reason to write a having clause. If the table where you want to select from is called 'MyTable', then your query is as follows:
SELECT MAX(message_id) AS top_message
FROM MyTable
WHERE child_id = '".$message_id[$i]."'
Note, that the paranthesis around child_id is not needed. Please read SQL and MySQL tutorials for more information, your life will be much easier.
I have a column in one of my table where I store multiple ids seperated by comma's.
Is there a way in which I can use this column's value in the "IN" clause of a query.
The column(city) has values like 6,7,8,16,21,2
I need to use as
select * from table where e_ID in (Select city from locations where e_Id=?)
I am satisfied with Crozin's answer, but I am open to suggestions, views and options.
Feel free to share your views.
Building on the FIND_IN_SET() example from #Jeremy Smith, you can do it with a join so you don't have to run a subquery.
SELECT * FROM table t
JOIN locations l ON FIND_IN_SET(t.e_ID, l.city) > 0
WHERE l.e_ID = ?
This is known to perform very poorly, since it has to do table-scans, evaluating the FIND_IN_SET() function for every combination of rows in table and locations. It cannot make use of an index, and there's no way to improve it.
I know you said you are trying to make the best of a bad database design, but you must understand just how drastically bad this is.
Explanation: Suppose I were to ask you to look up everyone in a telephone book whose first, middle, or last initial is "J." There's no way the sorted order of the book helps in this case, since you have to scan every single page anyway.
The LIKE solution given by #fthiella has a similar problem with regards to performance. It cannot be indexed.
Also see my answer to Is storing a delimited list in a database column really that bad? for other pitfalls of this way of storing denormalized data.
If you can create a supplementary table to store an index, you can map the locations to each entry in the city list:
CREATE TABLE location2city (
location INT,
city INT,
PRIMARY KEY (location, city)
);
Assuming you have a lookup table for all possible cities (not just those mentioned in the table) you can bear the inefficiency one time to produce the mapping:
INSERT INTO location2city (location, city)
SELECT l.e_ID, c.e_ID FROM cities c JOIN locations l
ON FIND_IN_SET(c.e_ID, l.city) > 0;
Now you can run a much more efficient query to find entries in your table:
SELECT * FROM location2city l
JOIN table t ON t.e_ID = l.city
WHERE l.e_ID = ?;
This can make use of an index. Now you just need to take care that any INSERT/UPDATE/DELETE of rows in locations also inserts the corresponding mapping rows in location2city.
From MySQL's point of view you're not storing multiple ids separated by comma - you're storing a text value, which has the exact same meaing as "Hello World" or "I like cakes!" - i.e. it doesn't have any meaing.
What you have to do is to create a separated table that will link two objects from the database together. Read more about many-to-many or one-to-many (depending on your requirements) relationships in SQL-based databases.
Rather than use IN on your query, use FIND_IN_SET (docs):
SELECT * FROM table
WHERE 0 < FIND_IN_SET(e_ID, (
SELECT city FROM locations WHERE e_ID=?))
The usual caveats about first form normalization apply (the database shouldn't store multiple values in a single column), but if you're stuck with it, then the above statement should help.
This does not use IN clause, but it should do what you need:
Select *
from table
where
CONCAT(',', (Select city from locations where e_Id=?), ',')
LIKE
CONCAT('%,', e_ID, ',%')
but you have to make sure that e_ID does not contain any commas or any jolly character.
e.g.
CONCAT(',', '6,7,8,16,21,2', ',') returns ',6,7,8,16,21,2,'
e_ID=1 --> ',6,7,8,16,21,2,' LIKE '%,1,%' ? FALSE
e_ID=6 --> ',6,7,8,16,21,2,' LIKE '%,6,%' ? TRUE
e_ID=21 --> ',6,7,8,16,21,2,' LIKE '%,21,%' ? TRUE
e_ID=2 --> ',6,7,8,16,21,2,' LIKE '%,2,%' ? TRUE
e_ID=3 --> ',6,7,8,16,21,2,' LIKE '%,3,%' ? FALSE
etc.
Don't know if this is what you want to accomplish. With MySQL there is feature to concatenate values from a group GROUP_CONCAT
You can try something like this:
select * from table where e_ID in (Select GROUP_CONCAT(city SEPARATOR ',') from locations where e_Id=?)
this one in for oracle ..here string concatenation is done by wm_concat
select * from table where e_ID in (Select wm_concat(city) from locations where e_Id=?)
yes i agree with raheel shan .. in order put this "in" clause we need to make that column into row below code one do that job.
select * from table where to_char(e_ID)
in (
select substr(city,instr(city,',',1,rownum)+1,instr(city,',',1,rownum+1)-instr(city,',',1,rownum)-1) from
(
select ','||WM_CONCAT(city)||',' city,length(WM_CONCAT(city))-length(replace(WM_CONCAT(city),','))+1 CNT from locations where e_Id=? ) TST
,ALL_OBJECTS OBJ where TST.CNT>=rownum
) ;
you should use
FIND_IN_SET Returns position of value in string of comma-separated values
mysql> SELECT FIND_IN_SET('b','a,b,c,d');
-> 2
You need to "SPLIT" the city column values. It will be like:
SELECT *
FROM table
WHERE e_ID IN (SELECT TO_NUMBER(
SPLIT_STR(city /*string*/
, ',' /*delimiter*/
, 1 /*start_position*/
)
)
FROM locations);
You can read more about the MySQL split_str function here: http://blog.fedecarg.com/2009/02/22/mysql-split-string-function/
Also, I have used the TO_NUMBER function of Oracle here. Please replace it with a proper MySQL function.
IN takes rows so taking comma seperated column for search will not do what you want but if you provide data like this ('1','2','3') this will work but you can not save data like this in your field whatever you insert in the column it will take the whole thing as a string.
You can create a prepared statement dynamically like this
set #sql = concat('select * from city where city_id in (',
(select cities from location where location_id = 3),
')');
prepare in_stmt from #sql;
execute in_stmt;
deallocate prepare in_stmt;
Ref: Use a comma-separated string in an IN () in MySQL
Recently I faced the same problem and this is how I resolved it.
It worked for me, hope this is what you were looking for.
select * from table_name t where (select (CONCAT(',',(Select city from locations l where l.e_Id=?),',')) as city_string) LIKE CONCAT('%,',t.e_ID,',%');
Example: It will look like this
select * from table_name t where ',6,7,8,16,21,2,' LIKE '%,2,%';