SQL query from two tables ordering results by both tables columns - php

I have two tables for registering out-going phone calls.
First table is people. The scheme is:
-----------------------------------
id | fname | lname | phone_number |
-----------------------------------
1 | John | Black | 132333312 |
2 | Marry | White | 172777441 |
-----------------------------------
Second tables called calls. The scheme is:
----------------------------------------------------------------------
id | scr_phone | dst_phone | dst_public_id | date | notes |
----------------------------------------------------------------------
1 | 555 | 132333312 | 1 | 1.12.2013 | chit-chat |
2 | 555 | 172777441 | 2 | 1.12.2013 | serios talk |
3 | 555 | 172777441 | 2 | 2.12.2013 | conversation|
----------------------------------------------------------------------
I'm displaying list of phones for users not in an alphabetical order but by frequency of calls. So whoever calls from local phone 555 sees at the top the people who he/she calls more often. Here is the MySQL query I use for it:
SELECT p.fname, p.lname, c.notes, COUNT(c.dst_public_id) AS amount
FROM people p
JOIN calls c ON c.dst_public_id = p.id
WHERE phones != ''
GROUP BY p.id
ORDER BY amount DESC, p.lname ASC
As a result I get person number 2 at the top of the phones list and person number 1 is on second place (I count how many calls are for each individual public and order it by that amount). Here is the query result:
--------------------------------
fname | lname | notes | amount |
--------------------------------
Marry | White | | 2 |
John | Black | | 1 |
--------------------------------
This is all good. But I want always to display last note made on last conersation with that person. I can probably make a totally separated MySQL query for each person requesting last record of each and getting the data from that, something like:
SELECT notes FROM calls WHERE dst_public_id = 2 ORDER BY date DESC LIMIT 1
... and then add the result of the first query inside my PHP script, but is there any way to do it with one single query?

You can get the last note with the following MySQL trick, using group_concat() and substring_index():
SELECT p.fname, p.lname, c.notes, COUNT(c.dst_public_id) AS amount,
substring_index(group_concat(notes order by id desc separator '^'), '^', 1) as last_notes
FROM people p JOIN
calls c
ON c.dst_public_id = p.id
WHERE phones != ''
GROUP BY p.id
ORDER BY amount DESC, p.lname ASC;
For this to work, you need a separator character that will never appear in the notes. I have chosen '^' for this purpose.

Can you try this? I haven't tested it as I don't have
SQL in front of me but you should get the idea.
SELECT tbl.notes, tbl.amount, p.fname, p.lname
FROM people p
join
(
select t.dst_public_id, c1.notes, t.cnt as amount
from calls c1 join
(
SELECT c2.dst_public_id, count(c2.id) as cnt, max(c2.id) as id
FROM
calls c2
group by c2.dst_public_id
) t on c1.id = t.id
) tbl on p.id = tbl.dst_public_id
WHERE p.phones != ''
GROUP BY p.id
ORDER BY tbl.amount DESC, p.lname ASC

Related

How to GROUP GROUP_CONCAT()?

I am looking for a way to get groups of the GROUP_CONCAT() function in a single query, for example.
My current code
SELECT
SUBSTRING_INDEX(GROUP_CONCAT(service_info.ip_address SEPARATOR ','),',',service_plans.aggregation) AS ip_address
FROM
services
LEFT JOIN
service_info
ON
service_info.service_id = services.id
LEFT JOIN
service_plans
ON
service_plans.id = services.service_plan_id
WHERE
service_plans.id = '2'
I want to group the IP addresses by a specific number(the $group_by variable if you see in the query) but then separate by a different character such as ":" or something.
Essentially I want my output to look like:
If $group_by=2: 10.1.1.2,10.1.1.3:10.1.1.4,10.1.1.5
If $group_by=3: 10.1.1.2,10.1.1.3,10.1.1.4:10.1.1.5
Is this possible to implement into my current query?
UPDATE: table structure
Table service_plans
id | name | aggregation
-----------------------------------------
1 | Uncapped 10Mbps 20:1 | 20
2 | Uncapped 20Mbps 10:1 | 10
3 | Capped 30Mbps | 0
Table services
id | service_plan_id | description
------------------------------------
1 | 2 | Phone
2 | 2 | Laptop
3 | 2 | PC
4 | 2 | TV
5 | 2 | Test
Table service_info
id | service_id | ip_address
------------------------------
1 | 1 | 10.1.1.2
2 | 2 | 10.1.1.3
3 | 3 | 10.1.1.4
4 | 4 | 10.1.1.5
5 | 5 | 10.1.1.6
I am trying to get an array of ip_address's concatenated and separated by a comma but the in groups of however much the service_plans.aggregation value is.
If aggregation is 2, then my output should be:
10.1.1.2,10.1.1.3:10.1.1.4,10.1.1.5
As you can see they are in groups of 2 and then the next group is separated by a colon(:)
If aggregation is 3, then my output should be:
10.1.1.2,10.1.1.3,10.1.1.4:10.1.1.5
As you can see they are in groups of 3 and then the next group is separated by a colon(:) and so on
Your post is a little confusing. What would be helpful is if you posted sample data, and then posted what you want your query to return. I'll give you an answer to what I think you're asking, based on the subject of your post.
ServicePlanIPs
service_plan_id | ip_address
-------------------------------
1 | 192.168.70.1
1 | 192.168.70.2
1 | 192.168.70.3
2 | 192.168.70.4
2 | 192.168.70.5
2 | 192.168.70.6
If you run this query against ServicePlanIPs:
SELECT service_plan_id, GROUP_CONCAT(ip_address) as ip_addresses
FROM ServicePlanIPs
GROUP BY service_plan_id
You will get:
service_plan_id | ip_addresses
-------------------------------
1 | 192.168.70.1, 192.168.70.2, 192.168.70.3
2 | 192.168.70.4, 192.168.70.5, 192.168.70.6
I don't guarantee this will run out of the box, but it should get you on the right track. Hope it helps. Note - if you're using a version of mysql which supports window functions, you can do something similar to the below and use the natively supported RANK function instead of doing it manually with variables.
SET #curRank := 0;
SET #concatIps := '';
SELECT
sp.id,
#curRank := #curRank + 1 AS rank,
IF(MOD(#curRank, (SELECT aggregation FROM service_plans WHERE id = {service_plan_id}) = 0, #concatIps := CONCAT(#concatIps, ':', s.ip_address), #concatIps := CONCAT(#concatIps, ',', s.ip_address))
FROM service_plans sp
JOIN services s
ON sp.id = s.service_plan_id
JOIN service_info si
ON si.service_id = s.id
WHERE sp.id = {service_plan_id}
ORDER BY service_info_id

MySQL Group By Concatenate

I have two SQL tables. customer and tag joined on the customer.id=tag.attach_id
customer
+------+-------------+--------------+
| id | name | email |
| 9 | Alan | alan#me.com |
+------+-------------+--------------+
tag
+------+-------------+--------------+
| id | attach_id | content |
| 1 | 9 | alan-tag |
| 2 | 9 | second-tag |
+------+-------------+--------------+
I want to output this:
+-------+-----------------+-----------------------+
| name | email | content |
+-------+-----------------+-----------------------+
| alan | alan#me.com | alan-tag, second-tag |
+-------+-----------------+-----------------------+
Here's my best attempt at SQL for this:
SELECT customer.name, customer.email, tag.content
FROM customer
INNER JOIN tag
ON customer.id=tag.attach_id
GROUP BY customer.id,tag.content;
Is this even possible without first processing the data in another language like PHP?
Yes you need to use GROUP_CONCAT as suggested by others on comment area, more specifically (exactly) your query is
SELECT `name`, email, GROUP_CONCAT(tag.content SEPARATOR ', ') as content
FROM
customer
INNER JOIN tag ON customer.id = tag.attach_id
GROUP BY customer.id
This Query will give you the exact result that you have osted on your post
You can use listagg if you are using sql
select c.name,email,listagg(content,',') within group (order by c.name) "content"
from customer c, tag t
where c.id = t.attach_id
group by c.name,email
Use this:
SELECT customer.name, customer.email, GROUP_CONCAT(tag.content SEPARATOR ', ') as content
FROM customer
INNER JOIN tag
ON customer.id=tag.attach_id
GROUP BY customer.id;

Converting PHP dependent SQL code to full SQL

the question will require a bit long of an answer to explain due to my ignorance on SQL.
I hope it will not be viewed as vague because I have tried doing it by parts, but then I wont know which part exactly is causing which problem.(It really shows my level of knowledge on SQL.)
I have a code that was originally written in a PHP file, but I have decided I want to create a view table in order for the page to load faster.
The reason was because it does a loop to list the ranking of students and was taking too long for the web page to load.
Anyways, here is the code :
SELECT
SUM(VCA.meritPoint) AS merit,
VCA.student_no AS student_no,
P.program_code AS education_level,
P.name AS name,
P.gender AS gender,
P.campus_id AS campus_id
FROM viewcardactivity VCA
JOIN pupil P ON P.student_no = VCA.student_no
JOIN semester S ON S.id = '{$id}' -- MAX() AND (MAX() - 1)
AND DATE(VCA.tarikh) BETWEEN DATE(s.tarikhStart) AND DATE(s.tarikhEnd)
WHERE P.campus_id = '{$campus}' -- 1, 2
AND P.gender= '{$gender}' -- M, F
AND VCA.level= '{$level}' -- Diploma, Degree
AND P.program_code = (CONVERT(IF((SUBSTR(REPLACE(`p`.`program_code`,' ',''),3,1) = 1),'Diploma','Degree')USING latin1))
GROUP BY student_no ORDER BY merit DESC
As the name of the columns suggests, I would like to display more than one instead of specific ids, gender and level provided from the PHP variables.
The example output I would like to have is such as(based on the SQL Fiddle mock data :
table 'viewrankingmerit'
| merit | student_no | education_level | name | gender | campus_id |
---------------------------------------------------------------------
| 99 | 111111111 | Diploma | Ash | M | 1 |
---------------------------------------------------------------------
| 87 | 222222222 | Diploma |Belle | F | 1 |
---------------------------------------------------------------------
| 85 | 333333333 | Degree | Carl | M | 1 |
---------------------------------------------------------------------
| 80 | 444444444 | Degree | Deli | F | 1 |
---------------------------------------------------------------------
| 75 | 555555555 | Diploma | Eddy | M | 2 |
---------------------------------------------------------------------
| 74 | 666666666 | Diploma |Foxxy | F | 2 |
---------------------------------------------------------------------
| 50 | 777777777 | Degree | Greg | M | 2 |
---------------------------------------------------------------------
| 20 | 888888888 | Degree |Haley | F | 2 |
---------------------------------------------------------------------
As for the semester id, I would like to get the latest 2 ids. Which is the highest and second highest, based on the auto-generated id that will keep on increasing..
I was immediately stuck at trying to get 2 ids from table semester. I've tried using :
JOIN semester S1 ON S1.id = (SELECT MAX(s1.id) FROM semester)
AND DATE(VCA.tarikh) BETWEEN DATE(s1.tarikhStart) AND DATE(s1.tarikhEnd)
JOIN semester S2 ON S2.id = (SELECT MAX(s2.id)-1 FROM semester)
AND DATE(VKA.tarikh) BETWEEN DATE(s2.tarikhStart) AND DATE(s2.tarikhEnd)
It was probably a bad reference, but that was the closest solution I got so far.
1) Is it possible to do a table to show all the info?
2) If yes, how to get both S.id, P.campus_id, P.gender and VCA.level. Hoping that the solution would be alike.
3) If no, what is the best solution?
Thanks a lot guys.
[Edit] I've added a demo data in an SQL Fiddle
After some discussion in coments, this is the final result. I think.
select sum(vca.meritPoint) as merit,
vca.student_no AS student_no,
vca.type AS education_level,
p.name AS name,
p.gender AS gender,
p.campus_id AS campus_id
from
viewcardactiviti vca
inner join pupil p ON p.student_no = vca.student_no
inner join (select * from semester order by id desc limit 2) s
ON (vca.tarikh between s.tarikhStart and s.tarikhEnd
AND vca.type = s.level)
group by vca.student_no, vca.type, p.name, p.gender, p.campus_id
order by merit desc, p.campus_id;
See it here on SQLFiddle
If you need to filter for specific configurations like the parameters on your original query just add a WHERE clause.
This subquery (select * from semester order by id desc limit 2) will get the last to semesters based on the ID. And since there is no direct link (foreign key) between semester and viewcardactiviti you can use there join conditions ON (vca.tarikh between s.tarikhStart and s.tarikhEnd AND vca.type = s.level)
If you think that it still need to change anything let me know!

Convert result from a left join to an array (or json or whatelse)

I actually have these tables :
- Table games -
ID
Name
- Table ean -
ID
ID_games
EAN
And I have this request :
SELECT games.*, ean.EAN
FROM games
LEFT JOIN ean ON (games.ID = ean.ID_games)
The result will be something like this :
| 1 | Half Life | 358958595 |
| 1 | Half Life | 589584859 |
| 2 | Half Life 2 | 385953684 |
| 2 | Half Life 2 | 585100335 |
etc.
When I do my request and use it in php, it is not useful to have a lot of line with about the same results. I would like to do something like this :
SELECT games.*, ConvertToArray(ean) AS ean_array
FROM games
LEFT JOIN ean ON (games.ID = ean.ID_games)
And have results like this :
| 1 | Half Life | (358958595,589584859) |
| 2 | Half Life 2 | (385953684,585100335 ) |
etc.
Is it possible to that with mysql ? Without an UDF ? And with an UDF ?
Thank you,
Kevin
Do you object to using GROUP_CONCAT? This would give you a nice delimited list (Assuming the ean value will never have your delimiter within):
SELECT games.*, GROUP_CONCAT(ean.EAN) AS ean_list,
FROM games
LEFT JOIN ean
ON games.ID = ean.ID_games
GROUP BY games.ID, games.name
Resulting in:
| 1 | Half Life | 358958595,589584859 |
| 2 | Half Life 2 | 385953684,585100335 |
Also, here's a qeustion I found with this logic applied: mySql - creating a join using a list of comma separated values
See GROUP_CONCAT().
Example can be found here.
Your query would look something like:
SELECT games.*, GROUP_CONCAT(DISTINCT ean) AS ean_array
FROM games
LEFT JOIN ean ON (games.ID = ean.ID_games)
GROUP BY games.ID
Try this one:
SELECT g.id, GROUP_CONCAT(CAST(e.ean AS CHAR) SEPARATOR ', ')
FROM games g
LEFT JOIN ean e ON g.id = e.id_games
GROUP BY g.id;

GROUP_CONCAT With Nested Set Model

I have an application that uses a nested set model class to organise my data, however I'm trying to write a query that will group_concat my results. I know I need to put some sub select statements somewhere but I can't figure it out!
Here's my structure at the moment:
table: person
-----------+------------+-----------
|Person_ID | Name | Age |
-----------+------------+-----------
| 1 | Mark Vance | 19 |
| 2 | Michael Tsu| 22 |
| 3 | Mark Jones | 29 |
| 4 | Sara Young | 25 |
-----------+------------+-----------
table: person_to_group
----+------------+-----------
|ID | Person_ID | Group_ID |
----+------------+-----------
| 1 | 3 | 1 |
| 2 | 3 | 2 |
| 3 | 1 | 2 |
| 4 | 4 | 3 |
----+------------+-----------
table: groups
----------+--------------+--------------+-------------
|Group_ID | Group_Name | Group_Left | Group_Right |
----------+--------------+--------------+-------------
| 1 | Root | 1 | 6 |
| 2 | Node | 2 | 5 |
| 3 | Sub Node | 3 | 4 |
----------+--------------+--------------+-------------
I need to render something like this with my results:
//Grab the group_IDs for this person and put them in the class tag...
<li class="2 3">Sara Young is in the Sub Node Group</li>
Notice that although Sara is in the Sub Node group, she is still being given the id for Node aswell because she is a child of Node.
The following is the query that I am working with as a starting point.
SELECT *, GROUP_CONCAT( CAST( gg.Group_ID AS CHAR ) SEPARATOR ' ' ) Group_IDs
FROM groups gg
LEFT JOIN person_to_group AS t1 ON gg.Group_ID = t1.Group_ID
LEFT JOIN person AS t2 ON t2.Person_ID = t1.Person_ID
GROUP BY t2.per_ID
ORDER BY t2.Name ASC
Any help would be much appreciated!
Here's how I'd write the query:
SELECT p.Name,
GROUP_CONCAT( g.Group_Name ) AS Group_List,
GROUP_CONCAT( CAST( gg.Group_ID AS CHAR ) SEPARATOR ' ' ) AS Group_ID_List
FROM person AS p
INNER JOIN person_to_group AS pg ON p.Person_ID = pg.Person_ID
INNER JOIN groups AS g ON pg.Group_ID = g.Group_ID
INNER JOIN groups AS gg ON g.Group_Left BETWEEN gg.Group_Left AND gg.Group_Right
GROUP BY p.Name
ORDER BY p.Name ASC
Note that if you group by person name, you also need to GROUP_CONCAT the list of group names. According to your schema, a person could belong to multiple groups, because of the many-to-many relationship.
I also recommend against using SELECT * in general. Just specify the columns you need.
This was little bit interesting as I do programming in both MsSQL and MySql. In SQL I have used function called STUFF. In MySQL you can use a function called INSERT. I tried out the below query in MsSQL. Don't have a MySQL handy to try out my query. If I have time I will post the MySQL version of the query.
DECLARE #person TABLE (Person_ID INT, Name VARCHAR(50), Age INT)
INSERT INTO #person VALUES
(1,'Mark Vance',19),
(2,'Michael Tsu',22),
(3,'Mark Jones',29),
(4,'Sara Young',25)
DECLARE #groups TABLE (Group_ID INT, Group_Name VARCHAR(50), Group_Left INT, Group_Right INT)
INSERT INTO #groups VALUES
(1,'Root',1,6),
(2,'Node',2,5),
(3,'Sub Node',3,4)
DECLARE #person_to_group TABLE (ID INT, Person_ID INT, Group_ID INT)
INSERT INTO #person_to_group VALUES
(1,3,1),
(2,3,2),
(3,1,1),
(4,4,1),
(4,1,1)
SELECT *,STUFF((SELECT ',' + CAST(g.Group_ID AS VARCHAR) FROM #groups g
JOIN #person_to_group pg ON g.Group_ID = pg.Group_ID AND pg.Person_ID = a.Person_ID FOR XML PATH('')) , 1, 1, '' ) FROM #person a
Function: INSERT(str,pos,len,newstr)
Documentation: http://dev.mysql.com/doc/refman/5.0/en/string-functions.html#function_insert

Categories