MySQL: Group by date proximity? - mysql

I wrote this query, it does almost what I want:
SELECT * FROM
(
SELECT COUNT(*) as cnt,
lat,
lon,
elev,
GROUP_CONCAT(CONCAT(usaf,'-',wban))
FROM `ISH-HISTORY_HASPOS`
GROUP BY lat,lon,elev
) AS x WHERE cnt >=1;
output:
+-----+--------+----------+--------+-------------------------------------------------+
| cnt | lat | lon | elev | GROUP_CONCAT(CONCAT(usaf,'-',wban)) |
+-----+--------+----------+--------+-------------------------------------------------+
| 4 | 30.478 | -87.187 | 36 | 722220-13899,722221-13899,722223-13899,999999-13899 |
| 4 | 36.134 | -80.222 | 295.7 | 723190-93807,723191-93807,723193-93807,999999-93807 |
| 5 | 37.087 | -84.077 | 369.1 | 723290-03849,723291-03849,723293-03849,724243-03849,999999-03849 |
| 5 | 38.417 | -113.017 | 1534.1 | 745200-23176,745201-23176,999999-23176,724757-23176,724797-23176 |
| 4 | 40.217 | -76.851 | 105.8 | 999999-14751,725110-14751,725111-14751,725118-14751 |
+-----+--------+----------+--------+-------------------------------------------------+
This returns a concatenated list of stations that are located at identical coordinates. However, I am only interested in concatenating stations with adjoining date ranges. The table that I select from (ISH-HISTORY_HASPOS) has two datetime columns : 'begin' and 'end'. I need the values for these two columns to be within 3 days of each other to satisfy the GROUP_CONCAT conditions.
Edit: In order for a station to be included in the final result's GROUP_CONCAT it must satisfy the following conditions:
It must be co-located with another station in the list (group by
lat,lon,elev)
Its end time must be within 3 days of another station's begin time OR its begin time must be within 3 days of another station's
end time. When I say "another station", I am referring to stations
that are co-located (meet the conditions for #1).
I figure that I will have to use a subquery but I can't seem to figure out how to do it. Some help would be greatly appreciated! Either a query or a stored procedure would be great but a php solution would also be acceptable.
Here is a dump of the table that I am querying:sql dump
The results should look the same as my example, but non-adjoining items (date-wise) should not be there.

A solution could be using a subquery to compute the list of station within 3 days of each other and adding this subquery as a where clause to the main query.
The subquery consists of a cartesian product to list all possible station couples with a first condition to get just the first half of the resulting matrix and two conditions to specify the time constraints. As to these latter conditions I just guessed them, I don't really know the begin and end fields unit of measure.
The resulting query could be this:
SELECT * FROM (
SELECT COUNT(*) AS
cnt,
lat,
lon,
elev,
GROUP_CONCAT(CONCAT(usaf, '-', wban))
FROM ISH-HISTORY_HASPOS
WHERE id IN (
SELECT DISTINCT t1.id
FROM ISH-HISTORY_HASPOS t1
INNER JOIN ISH-HISTORY_HASPOS t2
ON t1.lon = t2.lon
AND t1.lat = t2.lat
AND t1.elev = t2.elev
WHERE t1.id < t2.id
AND abs(t1.begin - t2.end) < 259200
AND abs(t1.end - t2.begin) < 259200
UNION
SELECT DISTINCT t2.id
FROM ISH-HISTORY_HASPOS t1
INNER JOIN ISH-HISTORY_HASPOS t2
ON t1.lon = t2.lon
AND t1.lat = t2.lat
AND t1.elev = t2.elev
WHERE t1.id < t2.id
AND abs(t1.begin - t2.end) < 259200
AND abs(t1.end - t2.begin) < 259200
)
GROUP BY lat, lon, elev
) AS x WHERE cnt >= 1;

I only have access and knowledge of SQL Server so I can't get your data to work and I don't know if MySQL has the equivalent functionality but here is a verbal description of what you need to do.
You need a recursive statement (WITH CTE in SQL Server) to join the table to itself on lat, lon, elev and begin BETWEEN end -3 AND end +3. You will need to be careful not to get caught in an infinite loop - I suggest building a comma seperated list of the IDs you have visited and checking this as you go. Its painful but keep this list in ID order becuase it is what you will need to group on at the end. You also need to keep track of your depth and the original id.
Something like ...
WITH cte(id, idlist, lat, lon, elev, starts, ends)
AS (
SELECT id, CAST(id AS varchar), lat, lon, elev, starts, ends
FROM `ISH-HISTORY_HASPOS`
UNION ALL
SELECT i.id, FunctionToManagetheList(i.idlist, cte.id), lat, lon, elev, starts, ends
FROM `ISH-HISTORY_HASPOS` i
INNER JOIN
cte ON i.lat=cte.lat AND
i.lon=cte.lon AND
i.elev=cte.elev AND
NOT FunctionToCheckIfTheIDisintheLitst(i.id, cte.idlist)
)
SELECT stuffyouneed
FROM `ISH-HISTORY_HASPOS` i
INNER JOIN
(SELECT id, MAX(depth) AS MaxDepth
FROM cte
GROUP BY id) cte1 ON i.id=cte.id
INNER JOIN
cte cte2 ON cte1.id=cte2.id AND cte1.MaxDepth=cte2.Depth
GROUP BY cte.idlist

Related

Calculate percentage in mySQL where SUM is already present in the table

I have a table(Which I have no control over) like this:
As, you can see this already has total calculate in a separate row
I have to do calculate percentage which should look something like this:
The issue is how do I pass Total in a sub query like
SELECT Marks from <TABLE> WHERE Topic = 'Total';
, so that I only get a single row?
Thanks
You can do something along the lines of
SELECT m1.*, ROUND(m1.marks / m2.marks * 100, 2) percentage
FROM marks m1 join marks m2
ON m1.name = m2.name AND m2.topic = 'Total'
ORDER BY name, topic
Output:
| Name | Topic | Marks | percentage |
|------|---------|-------|------------|
| Joe | Chem | 43 | 26.38 |
| Joe | Maths | 75 | 46.01 |
| Joe | Physics | 45 | 27.61 |
| Joe | Total | 163 | 100 |
...
SQLFiddle
The total SHOULD NOT be in the table. Given that you cannot modify it, I would just ignore that value and calculate the total and then calculate the percentage.
SELECT
m.Name,
Topic,
Marks,
Marks / t.Total * 100 AS Percentage
FROM
marks AS m
JOIN (
SELECT
Name,
SUM(Marks) AS Total
FROM
marks
WHERE
Topic != 'Total'
GROUP BY
Name) AS t ON t.Name = m.Name
In a subquery select the row with the same name and the topic 'Total'.
SELECT t1.name,
t1.topic,
t1.marks,
t1.marks
/ (SELECT t2.marks
FROM elbat t2
WHERE t2.name = t1.name
AND t2.topic = 'Total')
* 100 percentage
FROM elbat t1;
Another option is using a join.
SELECT t1.name,
t1.topic,
t1.marks,
t1.marks
/ t2.marks
* 100 percentage
FROM elbat t1
LEFT JOIN elbat t2
ON t2.name = t1.name
AND t2.topic = 'Total';
name is required to be unique and there must only be one row with 'Total' per name. Otherwise the subquery will throw an error about returning more than one row. With the join there's no such error but nonsense/ambiguous results.
You might also think about the case when there's a total of 0, as this would trigger a division by zero error.
The table design alas is bad. Tables represent relations, not spreadsheets. The rows with the total have no business being in there. Lookup relational normalization.

sql join data from two tables

I wonder if someone help me to join data from two tables...spending all the day didn't manage...
Code 1 selects:
Year | Turnover1 | Quantity1 | EurPerOrder1
SELECT Year(table1.ContractDate) AS Year,
Sum(table1.TPrice) AS Turnover1,
Count(table1.id) AS Quantity1,
ROUND(Sum(table1.TPrice) / Count(table1.id), 0) AS EurPerOrder1
FROM table1
GROUP BY Year(table1.ContractDate) * 100
ORDER BY table1.ContractDate DESC
Code2 selects:
Year | Turnover2 | Quantiry2 | EurPerOrder2
SELECT Year(table2.date) AS Year,
Sum(table2.price) AS Turnover2,
Count(table2.rid) AS Quantiry2,
ROUND(Sum(table2.price) / Count(table2.rid), 0) AS EurPerOrder2
FROM table2
GROUP BY Year(table2.date) * 100
ORDER BY table2.date DESC
And I need to join data like:
Year | Turnover1 | Quantity1 | EurPerOrder1 | Turnover2 | Quantiry2 | EurPerOrder2
I need to have all data from both tables grouped by years. Even table2 doesnt have year 2013 anyway I would like it showed 0 or empty...
I have tried different ways using examples but nothing worked so I think the problem can occur because second table doesn't have all the years which are on table1...
First: you can read pretty good explanation about the JOINS here
Ok, according the question you need LEFT JOIN. This means all data from table1 and only matching data from table2.
The SELECT must look like:
SELECT Year(table1.ContractDate) AS Year,
Sum(table1.TPrice) AS Turnover1,
Count(table1.id) AS Quantiry1,
ROUND(Sum(table1.TPrice) / Count(table1.id), 0) AS EurPerOrder1,
Sum(table2.price) AS Turnover2,
Count(table2.rid) AS Quantiry2,
ROUND(Sum(table2.price) / Count(table2.rid), 0) AS EurPerOrder2
FROM
table1 t1
LEFT JOIN table2 t2 ON Year(table1.ContractDate) = Year(table2.date)
GROUP BY
Year(table1.ContractDate) * 100, Year(table2.date) * 100
ORDER BY
table1.ContractDate DESC, table2.date DESC
Of course you need to process NULL values. See link
Please check SQL and correct it if there are erreors. I don't have live data to check (by running it).

Mysql each row sum

How can I get result like below with mysql?
> +--------+------+------------+
> | code | qty | total |
> +--------+------+------------+
> | aaa | 30 | 75 |
> | bbb | 20 | 45 |
> | ccc | 25 | 25 |
> +--------+------+------------+
total is value of the rows and the others that comes after this.
You can do this with a correlated subquery -- assuming that the ordering is alphabetical:
select code, qty,
(select sum(t2.qty)
from mytable t2
where t2.code >= t.code
) as total
from mytable t;
SQL tables represent unordered sets. So, a table, by itself, has no notion of rows coming after. In your example, the codes are alphabetical, so they provide one definition. In practice, there is usually an id or creation date that serves this purpose.
I would use join, imho usually fits better.
Data:
create table tab (
code varchar(10),
qty int
);
insert into tab (code, qty)
select * from (
select 'aaa' as code, 30 as qty union
select 'bbb', 20 union
select 'ccc', 25
) t
Query:
select t.code, t.qty, sum(t1.qty) as total
from tab t
join tab t1 on t.code <= t1.code
group by t.code, t.qty
order by t.code
The best way is to try both queries (my and with subquery that #Gordon mentioned) and choose the faster one.
Fiddle: http://sqlfiddle.com/#!2/24c0f/1
Consider using variables. It looks like:
select code, qty, (#total := ifnull(#total, 0) + qty) as total
from your_table
order by code desc
...and reverse query results list afterward.
If you need pure SQL solution, you may compute sum of all your qty values and store it in variable.
Also, look at: Calculate a running total in MySQL

MySQL: Add a "Total" Count to the last row of SQL Query for Unique value only

Info: Server version: 5.1.39 / MySQL / phpMyAdmin
Php: 5.4
Server: Apache
Code is run via: Server SQL Query (copy & paste in the phpMyAdmin) or in MySQL Workbench or using a custom shopping cart manager.
Exports to: Excel (.csv then to .xlsx for sales reports)
Other: I do use a number of tables for referencing
Question
I want to add a sub-total to the bottom (or top) row of my SQL Query. I am wanting to count Unique Order numbers only. Either as a whole, or by my date query.
This works, but puts it in 1 row, 1 column then does not generate the rest of my query.
COUNT( DISTINCT( T5.orders_id ) ) As OrdUnique,
Returns:
OrdUnique | OrdID | ProdName | etc
2342 | 21 | Name | Rest of data
What I would like is:
OrdID | ProdName | Qty | etc
2525 | prod | 1 |
2538 | prod | 1 |
2553 | prod | 1 |
2553 | prod | 1 |
2538 | prod | 1 |
OrdUnq = 3
The basic structure of my existing code is:
Select
T5.orders_id As OrdID,
T3.products_name As ProdName,
T2.products_quantity As Qty,
more content
even more content (about 70 lines of query)
ends with (similar)
From /*PREFIX*/products T1
Left Join /*PREFIX*/orders_products T2 On (T1.products_id = T2.products_id)
Inner Join /*PREFIX*/orders T5 On (T5.orders_id = T2.orders_id)
Left Join /*PREFIX*/manufacturers T4 On (T1.manufacturers_id = T4.manufacturers_id)
Where (T5.date_purchased >= 20120101) And (T5.date_purchased <= 20131216) And T5.orders_status = x
Order By T5.orders_id
Notes:
I do not run this via PHP, it is simply a copy & paste from my .sql/.txt file in to the backend of my server OR through MySQL Workbench
Throws a table access error
(select COUNT( DISTINCT( T5.orders_id ) ) from T5.orders) As OrdUnique,
Throws a Error Code: 1242: subquery returns more than one row
(select COUNT( DISTINCT( T5.orders_id ) ) as OrdUnq FROM orders GROUP BY orders_id WITH ROLLUP),
(as seen here: Calculate the total time duration on last row in mysql)
This also does not work:
Count unique records in database
Thank you in advance for your insight.
I know it is not very efficient, but an easy solution woudl be to use this query:
SELECT null as total,
T5.orders_id As OrdID,
T3.products_name As ProdName,
T2.products_quantity As Qty
From /*PREFIX*/products T1
Left Join /*PREFIX*/orders_products T2 On (T1.products_id = T2.products_id)
Inner Join /*PREFIX*/orders T5 On (T5.orders_id = T2.orders_id)
Left Join /*PREFIX*/manufacturers T4 On (T1.manufacturers_id = T4.manufacturers_id)
Where (T5.date_purchased >= 20120101)
And (T5.date_purchased <= 20131216)
And T5.orders_status = 'x'
Order By T5.orders_id
UNION
SELECT count(*) AS total,
null As OrdID,
null As ProdName,
null As Qty
FROM (select T5.orders_id
From /*PREFIX*/products T1
Left Join /*PREFIX*/orders_products T2 On (T1.products_id = T2.products_id)
Inner Join /*PREFIX*/orders T5 On (T5.orders_id = T2.orders_id)
Left Join /*PREFIX*/manufacturers T4 On (T1.manufacturers_id = T4.manufacturers_id)
Where (T5.date_purchased >= 20120101)
And (T5.date_purchased <= 20131216)
And T5.orders_status = 'x'
GROUP BY T5.orders_id
) as s
Pay attention to UNION and GROUP BY.

Identifying groups in Group By

I am running a complicated group by statement and I get all my results in their respective groups. But I want to create a custom column with their "group id". Essentially all the items that are grouped together would share an ID.
This is what I get:
partID | Description
-------+---------+--
11000 | "Oven"
12000 | "Oven"
13000 | "Stove"
13020 | "Stove"
12012 | "Grill"
This is what I want:
partID | Description | GroupID
-------+-------------+----------
11000 | "Oven" | 1
12000 | "Oven" | 1
13000 | "Stove" | 2
13020 | "Stove" | 2
12012 | "Grill" | 3
"GroupID" does not exist as data in any of the tables, it would be a custom generated column (alias) that would be associated to that group's key,id,index, whatever it would be called.
How would I go about doing this?
I think this is the query that returns the five rows:
select partId, Description
from part p;
Here is one way (using standard SQL) to get the groups:
select partId, Description,
(select count(distinct Description)
from part p2
where p2.Description <= p.Description
) as GroupId
from part p;
This is using a correlated subquery. The subquery is finding all the description values less than the current one -- and counting the distinct values. Note that this gives a different set of values from the ones in the OP. These will be alphabetically assigned rather than assigned by first encounter in the data. If that is important, the OP should add that into the question. Based on the question, the particular ordering did not seem important.
Here's one way to get it:
SELECT p.partID,p.Description,b.groupID
FROM (
SELECT Description,#rn := #rn + 1 AS groupID
FROM (
SELECT distinct description
FROM part,(SELECT #rn:= 0) c
) a
) b
INNER JOIN part p ON p.description = b.description;
sqlfiddle demo
This gets assigns a diferent groupID to each description, and then joins the original table by that description.
Based on your comments in response to Gordon's answer, I think what you need is a derived table to generate your groupids, like so:
select
t1.description,
#cntr := #cntr + 1 as GroupID
FROM
(select distinct table1.description from table1) t1
cross join
(select #cntr:=0) t2
which will give you:
DESCRIPTION GROUPID
Oven 1
Stove 2
Grill 3
Then you can use that in your original query, joining on description:
select
t1.partid,
t1.description,
t2.GroupID
from
table1 t1
inner join
(
select
t1.description,
#cntr := #cntr + 1 as GroupID
FROM
(select distinct table1.description from table1) t1
cross join
(select #cntr:=0) t2
) t2
on t1.description = t2.description
SQL Fiddle
SELECT partID , Description, #s:=#s+1 GroupID
FROM part, (SELECT #s:= 0) AS s
GROUP BY Description