Nested count() function with % percentage operation - php

I’m designing a program for my school to keep student attendance records. So far I have the following query working fine and now I would like to add an IF statement to perform a percentage operation when a certain condition is given. As it is, the query is using INNER JOIN to search for data from two different tables (oxadmain and stuattend) and it’s displaying the results well on a results table:
SELECT o.name
, o.year
, o.photoID
, o.thumbs
, s.ID
, s.studid
, s.date
, s.teacher
, s.subject
, s.attendance
FROM stuattend s
JOIN oxadmain o
ON s.studid = o.stuid
ORDER
BY name ASC
Now I would like to add an “if” statement that
1) finds when stuattend.attendance is = Absent, calculates the percentage of absences the students may have in any given period of time, and then stores that (%) value in “percentage” and
2) ELSE assigns the value of 100% to “Percentage”.
So far I’ve been trying with the following:
<?php $_GET['studentID'] = $_row_RepeatedRS['WADAstuattend']; ?>
SELECT oxadmain.name , oxadmain.year , oxadmain.photoID , oxadmain.thumbs , stuattend.ID , stuattend.studid , stuattend.date , stuattend.teacher, stuattend.subject , stuattend.attendance
CASE
WHEN stuattend.attendance = Absent THEN SELECT Count (studentID) AS ClassDays, (SELECT Count(*) FROM stuattend WHERE studentID = stuattend.studid AND Absent = 1) AS ClassAbsent, ROUND ((ClassAbsent/ClassDays)*100, 2) AS Percentage
ELSE
Percentage = 100
END
FROM stuattend INNER JOIN oxadmain ON stuattend.studid=oxadmain.stuid
ORDER BY name ASC
Any suggestions on how to do this well?
Thank you for your attention

The base idea would be:
select stuattend.studid, sum(stuattend.attendance = `absent`) / count(*)
from stuattend
group by stuaddend.studid;
This very much depends on exactly one entry per student and per day, and of course gets 0 if no absence and 1 if always absent.
To make this a bit more stable I would suggest to write a calendar day table, which simply keeps a list of all days and a column if this is a school day, so workday=1 means they should have been there and workday=0 means sunday or holiday. Then you could left join from this table to the presence and absence days, and even would give good results when presence is not contained in your table.
Just ask if you decide which way to go.

Related

Efficient comment system pagination query

So I've been looking around the web about any information about pagination.
From what I've seen there are 3 kinds, (LIMIT, OFFSET) a, (WHERE id > :num ORDER BY id LIMIT 10) b and (cursor pagination) c like those used on facebook and twitter.
I decided that for my project I'll go with the "b" option as it looks pretty straightforward and efficient.
I'm trying to create some kind of "facebook" like post and comment system, but not as complex.
I have a ranking system for the posts and comments and top 2 comments for each post that are fetched with the post.
The rest of the comments for each specific post are being fetched when people click on to see more comments.
This is a query for post comments:
SELECT
c.commentID,
c.externalPostID,
c.numOfLikes,
c.createdAt,
c.customerID,
c.numOfComments,
(CASE WHEN cl.customerID IS NULL THEN false ELSE true END) isLiked,
cc.text,
cu.reputation,
cu.firstName,
cu.lastName,
c.ranking
FROM
(SELECT *
FROM Comments
WHERE Comments.externalPostID = :externalPostID) c
LEFT JOIN CommentLikes cl ON cl.commentID = c.commentID AND cl.customerID = :customerID
INNER JOIN CommentContent cc ON cc.commentTextID = c.commentID
INNER JOIN Customers cu ON cu.customerID = c.customerID
ORDER BY c.weight DESC, c.createdAt ASC LIMIT 10 OFFSET 2
offset 2 is because there were 2 comments being fetched earlier as top 2 comments.
I'm looking for a way similar to this of seeking next 10 comments each time through the DB without going through all the rows like with LIMIT,OFFSET
The problem is that I have two columns that are sorting the results and I won't allow me to use this method:
SELECT * FROM Comments WHERE id > :lastId LIMIT :limit;
HUGE thanks for the helpers !
Solution So Far:
In order to to have an efficient pagination we need to have a single column with as much as possible unique values that make a sequence to help us sort the data and paginate through.
My example uses two columns to sort the data so it makes a problem.
What I did is combine time(asc sorting order) and weight of the comment(desc sorting order), weight is a total of how much that comment is being engaged by users.
I achieved it by getting the pure int number out of the DateTime format and dividing the number by the weight let's call the result,"ranking" .
this way a comment with a weight will always have a lower ranking ,than a comment without a weight.
DateTime after stripping is a 14 digit int ,so it shouldn't make a problem dividing it by another number.
So now we have one column that sorts the comments in a way that comments with engagement will be at the top and after that will come the older comments ,so on until the newly posted comments at the end.
Now we can use this high performance pagination method that scales well:
SELECT * FROM Comments WHERE ranking > :lastRanking ORDER BY ASC LIMIT :limit;
Ok i want to say about other way, in my opinion this very useful.
$rowCount = 10; //this is count of row that is fetched every time
$page = 1; //this is for calculating offset . you must increase only this value every time
$offset = ($page - 1) * $rowCount; //offset
SELECT
c.commentID,
c.externalPostID,
c.numOfLikes,
c.createdAt,
c.customerID,
c.numOfComments,
(CASE WHEN cl.customerID IS NULL THEN false ELSE true END) isLiked,
cc.text,
cu.reputation,
cu.firstName,
cu.lastName,
c.ranking
FROM
(SELECT *
FROM Comments
WHERE Comments.externalPostID = :externalPostID) c
LEFT JOIN CommentLikes cl ON cl.commentID = c.commentID AND cl.customerID = :customerID
INNER JOIN CommentContent cc ON cc.commentTextID = c.commentID
INNER JOIN Customers cu ON cu.customerID = c.customerID
ORDER BY c.ranking DESC, c.createdAt ASC LIMIT $rowCount OFFSET $offset
There can be an error because i didn't check it , please don't make it matter

Sum of 2 different non matching columns from 2 different tables

This s my first question on SO, so please bear if I am not super clear! I am trying to sum up values of 2 columns from 2 tables. Both table values have a common ID called 'imid'. These imids are further divided as 'pmid' (say 1 imid can have one or multiple pmids). Both tables have different structures. I would want to sum values on 1 column from table1 and another column from table(to use it for a php calculation). When I try JOIN it always gives me a timeout error. Query below.
SELECT F.`imid` AS imid, SUM( F.Impression ) AS si, D.accmgr AS accmgr, D.cmname AS cmname,sum(D.Item_Impression_Cap) AS sim
FROM trasactions F, rawdata D
WHERE F.`imid` = D.`imid`
AND F.EntryDate LIKE '2017-%-%'
GROUP BY D.`imid`, F.`imid` ORDER BY F.`imid` ASC
I get results, but not even close to the correct numbers.(eg) Impression as 6,557,824 instead of 1,233,287 for a particular imid.
Not sure where I am wrong! Any help would be great...
Edit: Thanks for the responses...I managed to write the query...
SELECT F.`imid`, F.si , F.imname,D.Item_Start_Date ,D.sim, D.Item_End_Date, D.accmgr, D.cmname
FROM (SELECT `imid` AS imid, SUM(Impression ) AS si, adname AS imname, EntryDate FROM trasactions GROUP BY imid) F LEFT JOIN
(SELECT imid,Item_Start_Date, Item_End_Date, accmgr AS accmgr, cmname AS cmname, sum(Item_Impression_Cap) AS sim FROM rawdata GROUP BY imid) D ON F.`imid`=D.`imid`
WHERE D.cmname IS NOT NULL
GROUP BY F.`imid`
ORDER BY F.`imid` ASC
Now there is a new question! I am doing some calculations based on the array values derived from the query above...(eg) $pacing = $row['si']/$avgday*100;
Say I am listing the 'Pacing' for all items active. Would it be possible to count values from variables. (eg) Would want to show the count of items where $pacing is less than 100%. Would it even be possible to do that!! Thanks again.

Select statement to display the result with combination of 2 columns

Hi I am new for developing.Kindly bear my codings. I have created a table arlog with id(auto increment), status, ticket number and code. Ticket and code number is set as unique. That is the duplicate of this combination cannot inserted again. But individually ticket number or cpt can be inserted as many times.It works fine. Now I want to use select query with another table with respect to the arlog table ticket number and code.Here is the select statement
$result = mysql_query("SELECT * FROM `ar` C WHERE provider='".$_SESSION['PROVIDER']
."' AND C.`TicketNo` IN ( SELECT TicketNo FROM `arlog` L where L.status NOT IN('New','Completed','Completed(Ar_aging)
','Completed(Rework)','Rework','Completed_Followup','Completed_Supervising' )
and L.assign='".$_SESSION['NAME']."' ) order by id desc") or die(mysql_error());
The query check the ticket number in arlog and displays correcly. But I want to combine TicketNo and Code in the arlog. I have made research but could not find solution. First of all is it possible?
Please try following sql:
SELECT L.TicketNo ,L.Code,C.* FROM `ar` C left join `arlog` L ON C.TicketNo = L.TicketNo where C.provider='your condition' and L.status NOT IN('New','Completed','Completed(Ar_aging)','Completed(Rework)','Rework','Completed_Followup','Completed_Supervising' ) and L.assign='your condition' order by by C.id desc
Hope this can help you!
I think you need to use CONCAT_WS()
There is a nice example of its usage in below link
MySQL SELECT AS combine two columns into one

How to calculate difference between values coming from the same row in mysql

I am trying to calculate the difference of values list coming from a database.
I would like to achieve it using php or mysql, but I do not know how to proceed.
I have a table named player_scores. One of its rows contains the goals scored.
Ex.
pl_date pl_scores
03/11/2014 18
02/11/2014 15
01/11/2014 10
I would like to echo the difference between the goals scored during the matches played in different dates.
Ex:
pl_date pl_scores diff
03/11/2014 18 +3
02/11/2014 15 +5
01/11/2014 10 no diff
How can I obtain the desired result?
You seem to want to compare a score against the score on a previous row.
Possibly simplest if done using a a sub query that gets the max pl_date that is less than the pl_date for the current row, then joining the results of that sub query back against the player_scores table to get the details for each date:-
SELECT ps1.pl_date, ps1.pl_scores, IF(ps2.pl_date IS NULL OR ps1.pl_scores = ps1.pl_scores, 'no diff', ps1.pl_scores - ps1.pl_scores) AS diff
FROM
(
SELECT ps1.pl_date, MAX(ps2.pl_date) prev_date
FROM player_scores ps1
LEFT OUTER JOIN player_scores ps2
ON ps1.pl_date > ps2.pl_date
GROUP BY ps1.pl_date
) sub0
INNER JOIN player_scores ps1
ON sub0.pl_date = ps1.pl_date
LEFT OUTER JOIN player_scores ps2
ON sub0.prev_date = ps2.pl_date
There are potentially other ways to do this (for example, using variables to work through the results of an ordered sub query, comparing each row with the value stored in the variable for the previous row)
SELECT score FROM TABLE WHERE DATE = TheDateYouWant
$score = $data['score'];
SELECT score FROM TABLE WHERE date = dateYouWant
$difference = $score - $data['score'];
Something like this?
You could use two queries, one to get the value to use in the comparison (in the example below is the smaller number of scores) and the second one to get the records with a dedicated column with the difference:
SELECT MIN(pl_scores);
SELECT pl_date, pl_scores, (pl_scores - minScore) as diff FROM player_scores;
Or, using a transaction (one query execution php side):
START TRANSACTION;
SELECT MIN(Importo) FROM Transazione INTO #min;
SELECT Importo, (Importo - #min) as diff FROM Transazione;
select *,
coalesce(
(SELECT concat(IF(t1.pl_scores>t2.pl_scores,'+',''),(t1.pl_scores-t2.pl_scores))
FROM tableX t2 WHERE t2.pl_date<t1.pl_date ORDER BY t2.pl_date DESC LIMIT 1)
, 'no data' ) as diff
FROM tableX t1
WHERE 1
order by t1.pl_date DESC

Need Help optimizing a complex MySQL query

I have this query below. There are 4 main tables involved: tblOrder, tblItems, tblOrder_archive, tblItem_archive. Orders and Items get moved over to the archived versions of the tables after a few months as not to slow down the main table queries. (sales and traffic is REALLY HIGH). So to get sales figures, i select what i need from each set of tables (archive and non archive).. union them.. do a group by on the union.. then do some math on the result.
Problem is that with any significant amount of rows (the order time span).. it will take so long for the query to run that it times out. I have added all the keys I can think of and still running super slow.
Is there more I can do to make this run faster? Can i write it differently? Can i use different indexes?
or should i write a script that gets the data from each table set first then does the math in the php script to combine them?
Thanks for the help.
SELECT
description_invoice
, supplier
, type
, sum(quantity) AS num_sold
, sum(quantity*wholesale) AS wholesale_price
, sum(quantity*price) AS retail_price
, sum(quantity*price) - sum(quantity*wholesale) AS profit
FROM (
SELECT
tblOrder.*
, tblItem.description_invoice
, tblItem.type
, tblItem.product_number
, tblItem.quantity
, tblItem.wholesale
, tblItem.price
, tblItem.supplier
FROM tblOrder USE KEY (finalized), tblItem
WHERE
tblItem.order_id = tblOrder.order_id
AND
finalized=1
AND
wholesale <> 0
AND (order_time >= 1251788400 AND order_time <= 1283669999)
UNION
SELECT
tblOrder_archive.*
, tblItem_archive.description_invoice
, tblItem_archive.type
, tblItem_archive.product_number
, tblItem_archive.quantity
, tblItem_archive.wholesale
, tblItem_archive.price
, tblItem_archive.supplier
FROM tblOrder_archive USE KEY (finalized), tblItem_archive
WHERE
tblItem_archive.order_id=tblOrder_archive.order_id
AND
finalized=1
AND
wholesale <> 0
AND (order_time >= 1251788400 AND order_time <= 1283669999)
) AS main_table
GROUP BY
description_invoice
, supplier,type
ORDER BY profit DESC;
Create indexes on the columns you are using in the WHERE clauses.
Remove the index hint: USE KEY (finalized). If it does anything at all it will probably just make it slower by causing MySQL to choose this key instead of a potentially better key.
Add a LIMIT to avoid fetching too many rows. Use paging if you want to see more rows.
Use UNION ALL instead of UNION. This will be faster because it doesn't check for duplicates and also you probably don't want to remove duplicates here anyway since this will affect the total.
Orders and Items get moved over to the archived versions of the tables after a few months as not to slow down the main table queries.
This is probably a bad idea. Instead you should index your data correctly so that the queries don't become significantly slower when you add more data. Or alternatively you could look at partitioning the table.
I re-wrote your query as:
SELECT COALESCE(x.description_invoice, y.description_invoice) AS description_invoice,
COALESCE(x.supplier, y.supplier) AS supplier,
COALESCE(x.type, y.type) AS type,
COALESCE(SUM(x.quantity), 0) + COALESCE(SUM(y.quantity), 0) as num_sold,
COALESCE(SUM(x.quantity * x.wholesale), 0) + COALESCE(SUM(y.quantity * y.wholesale), 0) AS wholesale_price,
COALESCE(SUM(x.quantity * x.price), 0) + COALESCE(SUM(y.quantity * y.price), 0) AS retail_price,
COALESCE(SUM(x.quantity * x.price), 0) - COALESCE(SUM(x.quantity * x.wholesale), 0) + COALESCE(SUM(y.quantity * y.price), 0) - COALESCE(SUM(y.quantity * y.wholesale), 0) as profit
FROM (SELECT o.order_id
FROM TBLORDER o
WHERE o.finalized = 1
AND o.order_time BETWEEN 1251788400
AND 1283669999
UNION ALL
SELECT oa.order_id
FROM TBLORDER_ARCHIVE oa
WHERE oa.finalized = 1
AND oa.order_time BETWEEN 1251788400
AND 1283669999) a
LEFT JOIN TBLITEM x ON x.order_id = a.order_id
AND x.wholesale != 0
LEFT JOIN TBLITEM_ARCHIVE y ON y.order_id = a.order_id
AND y.wholesale != 0
GROUP BY description_invoice, supplier, type
ORDER BY profit DESC
Your query had UNION, but I'd expect not to need duplicate removal from an archive table so I changed it to UNION ALL - which is faster, because it doesn't remove duplicates
For what you provided, you had SELECT ORDERS.* and SELECT ORDER_ARCHIVE.* but never used any of the columns.
The aggregation functions (SUM) were all on the TBLITEM table, which was unnecessarily within the derived table/inline view.
I omitted the USE KEY(finalized); you can re-add it if you like but I'd compare with and with out it - I'd suggest running ANALYZE TABLE occaissionally on both tables prior to running the query so the optimizer has relatively fresh statistics.
I don't see much value in an index on the finalized column, but I don't know your data or use - just this query. But based on this query, I'd index:
order_id
order_time
finalized
...as a covering index--a single index with three columns, in the order provided because order is important in a covering index.
I rewrote it as follows based on your help, and added the recommended covering index to both tblOrder and tblOrder archive and things seem to be much faster. But still i'm wondering if there something more to the way you wrote it.. but i would need to use tblItem_archive joined to tblOrder_archive as well.
SELECT
description_invoice
, supplier
, type
, sum(quantity) AS num_sold
, sum(quantity*wholesale) AS wholesale_price
, sum(quantity*price) AS retail_price
, sum(quantity*price) - sum(quantity*wholesale) AS profit
FROM (
SELECT
tblOrder.order_id
, tblItem.description_invoice
, tblItem.type
, tblItem.product_number
, tblItem.quantity
, tblItem.wholesale
, tblItem.price
, tblItem.supplier
FROM tblOrder, tblItem
WHERE
tblItem.order_id = tblOrder.order_id
AND
finalized=1
AND
wholesale <> 0
AND (order_time >= 1251788400 AND order_time <= 1283669999)
UNION ALL
SELECT
tblOrder_archive.order_id
, tblItem_archive.description_invoice
, tblItem_archive.type
, tblItem_archive.product_number
, tblItem_archive.quantity
, tblItem_archive.wholesale
, tblItem_archive.price
, tblItem_archive.supplier
FROM tblOrder_archive, tblItem_archive
WHERE
tblItem_archive.order_id=tblOrder_archive.order_id
AND
finalized=1
AND
wholesale <> 0
AND (order_time >= 1251788400 AND order_time <= 1283669999)
) AS main_table
GROUP BY
description_invoice
, supplier,type
ORDER BY profit DESC;

Categories