How do I generate several reports from one MySQL query? - php

I need to report the number of records that match each of several criteria. For example, how many customers live in each state. I know I can accomplish this through a series of MySQL SELECT statements, but that seems cumbersome and produces a ton of (unnecessary?) MySQL calls.
Can you tell me a better method to use? Can I query the database with one SELECT statement and then use PHP to filter the results to variables?

I'd suggest creating a view for this task just to hide the complexity of the query. Also, in the event that your table schema changes, it is likely that you are still going to want to retrieve this same information from the database. You'd be able to change the view in one place, instead of having to change the queries in, possibly, multiple places to satisfy your schema changes.
I'll just show you the queries, though, since you'd need to know how to do that to create a view anyways.
Sticking with your example of customers living in each state, let's pretend you also want statistics on how many customers share the same last name.
I've setup a mock structure of what your database might be like at this SqlFiddle.
Customers with Same LastName
The following query might be used to get the number of customers with the same last name:
SELECT
LastName AS "Value",
COUNT(*) AS "Count"
FROM Customers
GROUP BY
LastName;
Customers in Same State
Similarly, the customers in the same state might be retrieved with a query as follows:
SELECT
S.Name AS "Value",
COUNT(*) AS "Count"
FROM Customers AS C
INNER JOIN CustomerAddresses AS CA ON C.Id = CA.CustomerId
INNER JOIN Addresses AS A ON CA.AddressId = A.Id
INNER JOIN States AS S ON A.State = S.Id
GROUP BY
A.State;
Getting Your Desired Format
The format that you want is an aggregation of these two queries. You want both returned as a single result set. So, let's workout a schema for the returned table:
ResultType - This will hold a value that corresponds to the type of result. i.e. "State"
Value - This will hold the value of the aggregated column. i.e. "Florida"
Count - This will hold the total number of records that match the aggregated column.
So, now that we have a format, let's create a query that uses our two queries from above, and puts them into this format.
First, I add a new field to each of the above queries: ResultType
For example:
"LastName" AS "ResultType"
Now, I combine my queries into a single query using the UNION ALL statement:
SELECT * FROM (
/* LastName query */
SELECT
"LastName" AS "ResultType",
LastName AS "Value",
COUNT(*) AS "Count"
FROM Customers
GROUP BY
LastName
UNION ALL
/* States query */
SELECT
"State" AS "ResultType",
S.Name AS "Value",
COUNT(*) AS "Count"
FROM Customers AS C
INNER JOIN CustomerAddresses AS CA ON C.Id = CA.CustomerId
INNER JOIN Addresses AS A ON CA.AddressId = A.Id
INNER JOIN States AS S ON A.State = S.Id
GROUP BY
A.State
) AS A
In my SqlFiddle above, this produces an output like:
RESULTTYPE VALUE COUNT
=================================
LastName Jetson 1
LastName Johnson 2
LastName Milton 1
State Florida 2
State Georgia 1
State Utah 1
As you can see, this could get quite complex, so you might consider looking into placing this into a view. Then, you'd be able to query your view, as if it was the table above (ResultType, Value, and Count). That would also allow you to filter on it.

create select query make number of aliens of table and make your related columns aliens which is you want to use.
lest see sample example
SELECT a.id AS id
FROM Table1 AS a
WHERE ...
UNION ALL
SELECT b.name AS name
FROM Table2 AS b
WHERE ...
ORDER BY (a or b) ...

Related

How to Query and Arrange Two Similar Tables

I have one student table links to two scores tables which are exactly the same structures. The only different is that one table will stores high scores, the other table stores low scores. Now I need to query both high and low score tables, it will list all subjects scores with student name if it is from high score table and scores with name is student if its from low score table, and I need to order the result by time.
SELECT u.student_name,
a.subject1_score,
a.subject2_score,
a.subject3_score,
a.subject4_score,
a.subject5_score,
a.exam_date
FROM Student u
INNER JOIN High_Score_Table a
On u.student_id = a.student_id
ORDER BY a.exame_date = time
Then for low_score_Table I will have almost same query except that the student name will equal to Student by default.
Then I will need to put this together in a list and order by time. How could I do that shorter and better ?
Btw, I can merge two tables low and high_score into one, and add a column called "flag" into that, whenever the flag value equal to "show" then I show student name with all score records, else "hidden" I will just show "Student" and all score records. How could I do that in one query ?
It sounds like you need a UNION, because you are concatenating two distinct result sets - one from High_Score_Table and one from (presumably) Low_Score_Table:
select s.student_name,
h.subject1_score,
h.subject2_score,
h.subject3_score,
h.subject4_score,
h.subject5_score,
h.exam_date
from High_Score_Table h
join Student s on h.student_id = u.student_id
union all
select 'student' as student_name,
l.subject1_score,
l.subject2_score,
l.subject3_score,
l.subject4_score,
l.subject5_score,
l.exam_date
from Low_Score_Table l
order by exam_date
The takeaway here is an ORDER BY clause in a union sorts over the entire result set - which is in this case exactly what you want.

MySQL - structuring query to discard common results

That title is really not useful, but its a complex question (in my head, maybe) ... anywho...
Say I have a MySQL table of Countries (A-Z all countries in the world) with id & name
Then I have a table where I am tracking which countries a user has been to: Like so:
Country Table
id name
1 india
2 luxembourg
3 usa
Visited Table
id user_id country_id
1 1 1
2 1 3
Now here's what I want to do, when I present the form to add to the list of visited countries I want country.id 1 & 3 to be excluded from the query result.
I know I can filter this using PHP ... which is something I have done in the past ... but surely there must be a way to structure a query in such a way that 1 & 3 are excluded from the returned results, like:
SELECT *
FROM `countries`
WHERE `id`!= "SELECT `country_id`
FROM `visited`
WHERE `user_id`='1'"
I suspect it has something to do with JOIN statements but I can't quite figure it out.
Bonus gratitude if someone can point me in the right direction with Laravel.
Thanks you all :)
Is this what you want?
select c.*
from countries c left join
visited v
on c.id = v.country_id and v.user_id = 1
where v.country_id is null;
You can also express this as a not in or not exists, but the left join method typically has pretty good performance.
The left outer join keeps all records in the first table regardless of whether or not the on clause evaluates to true. If there are no matches in the second table, then the columns are populated with NULL values. The where clause simply chooses these records -- the ones that do not match.
Here is another way of expressing this that you might find easier to follow:
select c.*
from countries c
where not exists (select 1 from visited where c.id = v.country_id and v.user_id = 1)
You can use your query like this.
SELECT *
FROM `countries` c LEFT JOIN `visited` v on c.id = v.country_id
WHERE v.`country_id` is null
AND v.`user_id` = 1
This is a operation of a LEFT JOIN. What is means is that I'm selecting all registries from the table countries that may or may not is on the table visited based on the ID of the country.
So it will bring you this group
from country from visited
1 1
2 no registry
3 3
So on the where condition (v.country_id is null) I'm saying: I only want the ones that on this left join operation is only on the country table but it is not on visited table so it brings me the id 2. Plus the condition that says that those registries on visited must be from the user_id=1
SELECT * FROM COUNTRIES LEFT JOIN
VISITED ON
countries.id = visited.country_id and visited.country_id NOT IN ( SELECT country_id FROM visited )
if i understand right maybe you need something like this ?

Excess colomns in the query result

I'm trying to write a simple interface for a list of companies using MySQL and PHP. So, I want to fetch some information from my database.
Here are my tables:
companies_data - only for system information.
corporate_data - here I want to keep information about big companies.
individual_data - and here I want to keep information about little companies.
So, here is the tables
And here is the query that I've written:
SELECT
a.id,
a.user_id,
a.added,
a.`status`,
a.company_id,
a.company_type,
a.deposit,
a.individual_operations_cache,
a.corporate_operations_cache,
a.physical_operations_cache,
b.full_name,
b.tax_number,
b.address,
b.statement_date,
b.psrn,
c.full_name,
c.tax_number,
c.address,
c.statement_date,
c.psrn
FROM
companies_data a
LEFT OUTER JOIN corporate_data b
ON (a.company_id = b.id) AND a.company_type = 0
LEFT OUTER JOIN individual_data c
ON (a.company_id = c.id) AND a.company_type = 1
WHERE
a.user_id = 3
This is just the code for a test, I'll expand it soon.
As you see, I've got result with extra fields like %field_name%1, %another_field_name%1 and so on. Of course it is not the mysql error - what I've asked that I've got - but I want to remove this fields? It's possible or I must convert this output on the application side?
thos %field_name%1, %another_field_name%1 , are visible since you are selecting them in your query:
b.full_name,
b.tax_number,
b.address,
b.statement_date,
b.psrn,
c.full_name,
c.tax_number,
c.address,
c.statement_date,
c.psrn
When you use fields with the same name in distinct tables, then the result column name come with this identifier field1, field2, fieldn... in order to distinguish from which table does the field come from.
If you want to avoid this names, you can use aliases as follows:
[...]
b.full_name as corporate_full_name,
[...]
Probably, if every common fields are coincident, you won´t need to show them all, so just remove them from the select.
Hope being usefull for you.
Br.

Complex SQL query, need to sort via count based upon time constraints

Hi guys I have the following three tables here.
COUNTRIES
ID | Name | Details
Airports
ID | NAME | CountryID
Trips
ID | AirportID | Date
I have to retrieve a list showing the following:
AirportID | AIrport Name | Country Name | Number of Trips Made Between Date1 and Date2
I need this to be really efficient, what kind of indexes do I need to set up and how would I formulate the SQL query here? I would be displaying this using Php. Note that I need to be able to sort based upon the number of trips made.
EDIT ==
Oops forgot to mention my sql:
I've tried the following:
SELECT `c`.*, `t`.`country` AS `country_name`, COUNT(f.`id`) AS `num_trips` FROM `airports` AS `c`
LEFT JOIN `countries` AS `t` ON t.`id` = c.`country_id`
LEFT JOIN `trips` AS `f` ON f.`airportid` = c.`id` GROUP BY `c`.`id` ORDER BY `num_flights` ASC LIMIT 10
It works but takes a really looong time to execute - plus consider this that my airports table has over 30'000 entries and teh trips table is variable.
I'm just taking the name of the country from the countries table - would it be better if I were to instead exclude joining teh countries table in the sql and instead retrieve the country name from an array where the index is the ID and values are the names of countries?
I'm not sure why you're using left joins. If every trip has an airport and every airport has a country, and inner join would give you accurate results.
I would do this:
select a.ID as AirportID, a.Name as AirportName, c.Name as CountryName, count(t.id) as NumTrips
from Trips t
inner join Airports a on t.AirportID = a.ID
inner join Countries c on a.CountryID = c.ID
where t.Date >= #StartDate
and t.Date <= #EndDate
group by AirportID, AirportName, CountryName
order by NumTrips
limit 10
Replace the #StartDate and #EndDate with your appropriate values.
Not sure what you're looking for in results, but I would expect you want the most trips. In that case you would want to do "order by NumTrips desc". This will show the highest values first, especially since you're limiting it to 10.
Also, I suggest you rename your "Date" column to something that won't collide with reserved SQL words. I usually use "DateCreated" or "DateOfTravel" or something like that.
If I made any poor assumptions let me know and I can re-write this.
Edit:
For indexes, create them on fields you will be looking up on. In other words, primary keys (which should always be indexed), foreign keys, and in this case it looks like the Date column would be the other important index. However, if you plan on searching by "Airport Name", then add an index there. I think you see where this is headed, etc.
Indexes on airpoirt(countryid, id) and trips(airportid) would seem the most important.
Instead of count(f.id) try count(f.airportid), so MySQL doesn't have to check the trips.id column.

Joining multiple (4) tables in MYSQL

I have four tables I want to join and get data from. The tables look something like...
Employees (EmployeeID, GroupID[fk], EmployeeName, PhoneNum)
Positions (PositionID, PositionName)
EmployeePositions (EployeePositionID, EmployeeID[fk], PositionID[fk])
EmployeeGroup (GroupID, GroupName)
[fk] = foreign key
I want to create a query that will return all the information about an employee(given by EmployeeID). I want a query that will return the given employees Name, position(s), and group in one row.
I think it needs to involve joins, but I am not sure how to format the queries. MYSQL's manual is technical beyond my comprehension. I would be very grateful for any help.
It seems you have trouble with SQL, in general, rather than with mySQL in particular. The documentation of mySQL provides details about the various SQL expressions, but generally assume some familiarity with SQL. To get a quick start on SQL you may consider this W3schools.com primer.
The query you need is the following.
SELECT EmployeeName, PositionName, GroupName
FROM Employees E
LEFT JOIN EmployeePositions EP ON EP.EmployeeID = E.EmployeeID
LEFT JOIN Positions P ON P.PositionID = EP.PositionId
LEFT JOIN EmployeeGroup EG ON EG.GroupId = E.GroupId
WHERE E.EmployeeId = some_value
A few things to note:
The 'LEFT' in 'LEFT JOIN' will result in producing NULL in lieu of PositionName or GroupName when the corresponding tables do not have a value for the given FK. (Should only happen if the data is broken, say if for example some employees have GroupId 123 but somehow this groupid was deleted from the EmployeeGroup table.
The query returns one line per employee (1). You could use an alternative search criteria, for example WHERE EmployeeName = 'SMITH', and get a listing of all employees with that name. Indeed without a WHERE clause, you'd get a list of all employees found in Employees table.
(1) that is assuming that each employee can only have one position. If somehow some employees have more than one position (i.e. multiple rows in EmployeePositions for a given EmployeeID), you'd get several rows per employee, the Name and Group being repeated and a distinct PostionName.
Edit:
If a given employee can have multiple positions, you can use the query suggested by Tor Valamo, which uses a GROUP BY construct, with GROUP_CONCAT() to pivot all the possible positions in one single field value in the returned row.
SELECT e.EmployeeID, e.EmployeeName, e.PhoneNum,
g.GroupName, GROUP_CONCAT(p.PositionName) AS Positions
FROM Employees e
LEFT JOIN EmployeeGroup g ON g.GroupID = e.GroupID
LEFT JOIN EmployeePositions ep ON ep.EmployeeID = e.EmployeeID
LEFT JOIN Positions p ON p.PositionID = ep.PositionID
WHERE e.EmployeeID = 1
GROUP BY e.EmployeeID
Returns positions in a comma separated string on one row.

Categories