Postgresql usage of aggregates and non-aggregate columns, grouping by - php

Some things in lecture and in my lab assignment were not explained very well. I am having trouble displaying the correct information.
Here is the database info, simply a reference for you to help me.
The database tables info
This is the query that I am trying to execute
The postgresql php select statement
This results in this SQl error being throwned
Connected to database!
Query failed: ERROR: column "city.name" must appear in the GROUP BY clause or be used in an aggregate function LINE 3: city.name ^
Now if I do add city.name into the GROUP BY clause, it returns 4096 rows! I dont want that to happen, the results have to be group by country name which is 232 rows. I simply want to display the country name, city name, and the city with the highest population in that country. City name is throwing me off, Im guessing there is a more complicated more syntax heavy solution.
Thanks for any help you can provide.
-Tom Reese

You need something like this:
select
country.name,
city.name,
mp.maxpop
from
lab6.country,
lab6.city,
(select
country_code,
max(population) as maxpop
from
lab6.city
group by
country_code
) mp
where
country.country_code=mp.country_code and
country.country_code=city.county_code and
mp.maxpop=city.population
notes:
This can give you more result/county.
Your original query doesn't work because in ansi sql you can't return only aggregated or group by expressions from a "group by" query. (As the error mentions)

Related

What is the best way to do the job of Group By in mysql when sql_mode=only_full_group_by

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

Query selected columns from two tables with a Condition clause

I have two tables-
1) ****Company_Form****
[Contract_No#,Software_Name,Company_Name,Vendor_Code]
2) ****User_Form****
[Contract_No,Invoice_No#,Invoice_Date,Invoice_Amount,Invoice_Submit_Date]
Fields denoted with # and bold are primary keys.
=>The user has to enter a software name for which he wants to get the data of.
=>I have to structure a query in which I have to display the result in the following form:
[Contract#,Software_Name,Company_Name,Invoice_No,Invoice_Date,Invoice_Submission_Date]
Now,
one Contract_No can contain many Invoice_no under its name in
the User Form table.
One Contract_No can occur one time only in
Company_Form table
The retrieved records have to be group by the latest Invoice_Date
I came to the logic that:
I have to first retrieve all the contract numbers with that software
name from Company_Form table.
I have to query that contract number from User_Form table and display
the data for each matched contract no. fetched from Company_Form
table.
The problem is that I am unable to structure a query in SQL that can do the task for me.
Kindly help me in formulating the query.
[PS] I am using SQL with PHP.
I tried a query like:
I tried one approach as :
SELECT a.ContractNo,a.SoftwareName,a.CompanyName,b.InvoiceNo,b.InvoiceDate,b.InvAmount,b.InvoiceSubmitDate
FROM Company_Form as a,User_Form as b
WHERE b.ContractNo IN(SELECT ContractNo FROM Company_Form WHERE
SoftwareName='$Sname') AND a.ContractNo=b.ContractNo;
But I am getting a error that sub query returns more than 1 row.
Can I get help from this?
I am assuming you are attempting to find the most recent price of the user selected software and its corresponding invoice. Here is an approach to do this. If this is tested to your satisfaction, I can add necessary explanation.
select uf.Contract_No#,
cf.Software_Name,
cf.Company_Name,
uf.Invoice_No#,
uf.Invoice_Date,
uf.Invoice_Amount,
uf.Invoice_Submit_Date
from User_Form uf
inner join (
-- Most recent sale of software
select Contract_No#, max(Invoice_Date)
from User_Form
group by Contract_No#
) latest
on (
-- Filter via join for latest match records
uf.Contract_No# = latest.Contract_No#
and uf.Invoice_Date = latest.Invoice_Date
)
inner join Company_Form cf
on cf.Contract_No# = uf.Contract_No#
where cf.Software_name = :software_name
If the requirement allows your sub query to return more than one row, I would suggest you to use IN instead of = in the where clause of your main query.
Please note that I have just looked at the query and have not fully understood the requirements.
Thanks.
I worked around for some time and finally came to the following query which works like a charm
SELECT a.ContractNo,a.SoftwareName,a.CompanyName,b.InvoiceNo,b.InvoiceDate,b.InvAmount,b.ISD
FROM Company_Form as a,User_Form as b
WHERE b.ContractNo IN (SELECT ContractNo FROM Company_Form WHERE SoftwareName='$Sname')
AND a.ContractNo=b.ContractNo;
If anybody needs help in understanding the logic of this query,feel free to comment below.

MySQL Column names of any Select statement

I am making a SQL teaching program. The students will become their Database and Questions. I want to make it possible for them to write any query they want to and give them the result of it.
So it is possible that they write a query like:
SELECT Person.*, Student.ID
FROM Person JOIN Student JOIN Address
WHERE Address.housenr > 20
This makes it hard to use the
SELECT table_name, column_name
FROM INFORMATION_SCHEMA.COLUMNS;
to get the column names. It would be very nice to get those names right from the results of the query. Is it possible to do that in PHP?
You can use the fetch_fields() method to get any information about the columns in your result set. When your query returns at least one row you can use the normal fetch_assoc() method and read the keys from the returned array via array_keys().

Database Group By error

I've been working with Mysql for a while, but this is the first time I've encountered this problem.
The thing is that I have a select query...
SELECT
transactions.inventoryid,
inventoryName,
inventoryBarcode,
inventoryControlNumber,
users.nombre,
users.apellido,
transactionid,
transactionNumber,
originalQTY,
updateQTY,
finalQTY,
transactionDate,
transactionState,
transactions.observaciones
FROM
transactions
LEFT JOIN
inventory ON inventory.inventoryid = transactions.inventoryid
LEFT JOIN
users ON transactions.userid = users.userid
GROUP BY
transactions.transactionNumber
ORDER BY
transactions.inventoryid
But the GROUP BY is eliminating 2 values from the QUERY.
In this case, when I output:
foreach($inventory->inventory as $values){
$transactionid[] = $values['inventoryid'];
}
It returns:
2,3,5
If I eliminate the GROUP BY Statement it returns
2,3,4,5,6
Which is the output I need for this particular case.
The question is:
Is there a reason for this to happen?
If I'm grouping by a transaction and that was supposed to affect the query, wouldn't it then return only 1 value?
Maybe I'm over thinking this one or been working too long on the code that I don't see the obvious flaw in my logic. But if someone can lend me a hand I would appreciate it.
In standard SQL you can only SELECT colums which are
contained in GROUP BY clause
(or) aggregate "colums", like MAX() or COUNT().
You need to consult the MySQL description of the interpretation they use for columns which are not contained by GROUP BY (and which are no aggregated column) MySQL Handling of GROUP BY to find out what happens here.
Do you need more information?

Is there anyway to tell which fields are grouped fields in a MySQL result (PHP)

I have some PHP code that will run user-generated SQL on a MySQL table. The query possibilities are limited, but could be, for example
SELECT City, Country, count(*) FROM Table ... GROUP BY City, Country
or
SELECT count(*) AS Count, Role, Country FROM Table ... GROUP BY Role, Country
or
SELECT count(*), TicketType, City, SUM(Quantity) AS 'Total Quantity' FROM Table ... GROUP BY TicketType, City
and so on.
When presenting the results of the query, I want to take one type of action if the column is an grouped field (e.g. COUNT or SUM or AVG etc.)
Other than parsing the query, is there any way to determine which fields are grouped fields from the result set? I am using mysqli and PHP
it is simple I guess, the only fields you can select in SELECT clause are the fields that are either grouped in Group By clause or grouped by aggregated functions.
So when you use
SELECT City, Country, count(*) FROM Table ... GROUP BY City, Country
your result set will have multiple rows each with three columns:city,country,count(*). city and country are the fields which are grouped by.
Generally the answer is no.
What you receive from the db are field names and values. Without parsing the original query you can't be sure which fields are aggregations and which are not.
Probably in your case there can be constraints you can use. For example:
If numbers can be only generated fields you can check the contents of the result. (Of course this is very unstable.)
Maybe you can use the field's name as a hint. Maybe it contains something related to aggregations. (sum, count, total,...)
But probably to best/most stable way to handle your problem to get more control around the query generation, and set explicit constraints you need or save the details you need later.
Maybe you can try to use this function: http://www.php.net/manual/en/pdostatement.getcolumnmeta.php
But I don't think it will be satisfying.

Categories