I'm finishing up a system for buying event tickets, and I need to be able to check if there are still tickets available.
I have a table (tbl_rel_order_status) that makes a relation between orders and new statuses received from a payment gateway (like, say, PayPal). Every new transaction generates a new entry.
Structure and example for tbl_rel_order_status (tbl_order_id and tbl_status_order_id are FKs)
| id | tbl_order_id | tbl_status_order_id | datetime |
| 1 | 1 | 1 | 2016-08-01 18:20:00 |
| 2 | 1 | 3 | 2016-08-01 18:20:00 |
Structure and example for tbl_order (tbl_ticket_id, tbl_client_id and tbl_payment_method_id are FKs)
| id | tbl_ticket_id | tbl_client_id | tbl_payment_method_id | qnty |
| 1 | 1 | 1 | 1 | 40 |
| 2 | 1 | 2 | 1 | 3 |
I have a few "cancellation statuses" (namely: 6,7,8,11,13,15), which all mean that for some reason that amount of tickets can be made available again for other clients.
Here's the thing: I'm working on a query to retrieve the quantities of tickets which have as latest status any of the mentioned above. If not, the query should skip to the next ticket (that's where I'm stuck).
SELECT tbl_order.qnty
FROM (SELECT tbl_order_id,tbl_status_order_id
FROM tbl_rel_order_status
WHERE tbl_status_order_id IN ('6','7','8','11','13','15')
ORDER BY id DESC) inside
INNER JOIN tbl_order on tbl_order.id = inside.tbl_order_id
GROUP BY tbl_status_order_id
With that query I can only get the ones that DO have any of these, but not for the latest (or newest) DB entry.
SQL Fiddle
Any clues?
You can get the latest status using a query like this:
select o.*,
(select ros.tbl_status_order_id
from tbl_rel_order_status ros
where ros.tbl_order_id = o.id
order by ros.datetime desc
limit 1
) as last_status
from tbl_order o;
From this, I think you want:
select o.*,
(select ros.tbl_status_order_id
from tbl_rel_order_status ros
where ros.tbl_order_id = o.id
order by ros.datetime desc
limit 1
) as last_status
from tbl_order o
having last_status in ('6', '7', '8', '11', '13', '15');
Related
I would like to calculate the Click-Through Ratio (CTR) of several articles of a website using SQL.
The formula of the CTR is CTR = number clicks / number impressions, i.e. a ratio of how many times an article has been clicked and how many times it has been shown.
I have two tables:
´article_click´: A table with several columns, namely ´article_id´ (denoting the id of the article), ´description´ (a brief description of the article), ´timestamp´ (when it has been clicked), among others. Every time a user clicks an article, a new row is created in the table.
´article_impression´: Similarly, a table with several columns, namely ´article_id´ (denoting the id of the article), ´description´ (a brief description of the article), ´timestamp´ (when it has been shown), among others. Every time an article is shown to a user, a new row is created in the table.
Both tables 1 and 2 look like this:
+------------+-------------+------------------+-----+
| article_id | description | timestamp | ... |
+------------+-------------+------------------+-----+
| 102 | Potatoe | 2021-01-01 13:45 | ... |
| 11 | Lettuce | 2020-02-11 11:00 | ... |
| 34 | Train | 2019-12-12 09:31 | ... |
| 21 | Car | 2011-11-11 08:32 | ... |
| 201 | Train | 2014-02-10 02:12 | ... |
| ... | ... | ... | ... |
+------------+-------------+------------------+-----+
And I would like to create a table such that:
+------------+-----+
| article_id | CTR |
+------------+-----+
| 11 | 0.4 |
| 23 | 0.6 |
| 34 | 0.2 |
| 44 | 0.8 |
| 45 | 0.3 |
| ... | ... |
+------------+-----+
In order to do so, I have tried:
SELECT article_click.article_id, COUNT(article_click.article_id) / COUNT(article_impression.article_id) AS CTR
FROM article_click
INNER JOIN article_impression ON article_click.article_id = article_impression.article_id
GROUP BY article_click.article_id DESC;
But I obtain something like:
+------------+-----+
| article_id | CTR |
+------------+-----+
| 11 | 1.0 |
| 23 | 1.0 |
| 34 | 1.0 |
| 44 | 1.0 |
| 45 | 1.0 |
| ... | ... |
+------------+-----+
Can anyone spot the mistake here? I'm using MySQL as RDBMS.
If the click-through-rate (CTR) is number clicks / number impressions then you'll need to calculate the number of clicks on an article and the number of impressions on an article before joining them to perform the calculation.
You could do this with subqueries or CTEs, but I've opted for the former here.
SELECT c.article_id, c.click_count / i.impression_count AS CTR
FROM (
SELECT article_id, COUNT(*) AS click_count
FROM article_click
GROUP BY article_id) AS c
INNER JOIN (
SELECT article_id, COUNT(*) AS impression_count
FROM article_impression
GROUP BY article_id) AS i
ON c.article_id = i.article_id;
Try it out on SQL Fiddle.
Note that using an INNER JOIN will exclude articles that have impressions but no clicks, so you won't get results where the CTR is 0. If you want those, you can use a LEFT JOIN from impressions to clicks. Since an article cannot be clicked if it has not been shown, we know that a LEFT JOIN from impressions to clicks is sufficient to show all data.
SELECT i.article_id, COALESCE(c.click_count, 0) / i.impression_count AS CTR
FROM (
SELECT article_id, COUNT(*) AS impression_count
FROM article_impression
GROUP BY article_id) AS i
LEFT JOIN (
SELECT article_id, COUNT(*) AS click_count
FROM article_click
GROUP BY article_id) AS c
ON i.article_id = c.article_id;
Note that we have to use the article_id from article_impression since article_click might be NULL. For the same reason, we have to COALESCE the click_count so that we don't end up with an error trying to divide NULL.
Before using joins duplicate data must be avoided. Get individual counts of each table and join both the queries.
select a.article_id, article_click/article_impression_click as ctr
from ( select a.article_id, count(a.article_id) article_click from
article_click a group by article_id) a inner join (select
a.article_id, count(a.article_id) article_impression_click from
article_impression a group by article_id) b on
a.article_id=b.article_id
WITH
v_article AS
( SELECT 'S' type, article_impression.id FROM article_impression
UNION ALL
SELECT 'C' type, article_click.id FROM article_click
)
SELECT
v_article.id,
COUNT(CASE WHEN v_article.type = 'C' THEN 1 END) nb_show,
COUNT(CASE WHEN v_article.type = 'S' THEN 1 END) nb_click,
CASE
WHEN COUNT(CASE WHEN v_article.type = 'S' THEN 1 END) > 0 THEN
ROUND(COUNT(CASE WHEN v_article.type = 'C' THEN 1 END) / COUNT(CASE WHEN v_article.type = 'S' THEN 1 END), 2)
END ratio_click_show
FROM v_article
GROUP BY
v_article.id
;
If you're sure an article can be click only if it has been previously shown (nb_show > 0 and nb_show > nb_click), you can remove the CASE around the ratio calculation.
I need help to optimize my 3 queries into one.
I have 2 tables, the first has a list of image processing servers I use, so different servers can handle different simultaneous job loads at a time, so I have a field called quota as seen below.
First table name, "img_processing_servers"
| id | server_url | server_key | server_quota |
| 1 | examp.uu.co | X0X1X2XX3X | 5 |
| 2 | examp2.uu.co| X0X1X2YX3X | 3 |
The second table registers if there is a job being performed at this moment on the server
Second table, "img_servers_lock"
| id | lock_server | timestamp |
| 1 | 1 | 2020-04-30 12:08:09 |
| 2 | 1 | 2020-04-30 12:08:09 |
| 3 | 1 | 2020-04-30 12:08:09 |
| 4 | 2 | 2020-04-30 12:08:09 |
| 5 | 2 | 2020-04-30 12:08:09 |
| 6 | 2 | 2020-04-30 12:08:09 |
Basically what I want to achieve is that my image servers don't go past the max quota and crash, so the 3 queries I would like to combine are:
Select at least one server available that hasn't reached it's quota and then insert a lock record for it.
SELECT * FROM `img_processing_servers` WHERE
SELECT COUNT(timestamp) FROM `img_servers_lock` WHERE `lock_server` = id
! if the count is < than quota, go ahead and register use
INSERT INTO `img_servers_lock`(`lock_server`, `timestamp`) VALUES (id_of_available_server, now())
How would I go about creating this single query?
My goal is to keep my image servers safe from overload.
Join the two tables and put that into an INSERT query.
INSERT INTO img_servers_lock(lock_server, timestamp)
SELECT s.id, NOW()
FROM img_processing_servers s
LEFT JOIN img_servers_lock l ON l.lock_server = s.id
GROUP BY s.id
HAVING IFNULL(COUNT(l.id), 0) < s.server_quota
ORDER BY s.server_quota - IFNULL(COUNT(l.id), 0) DESC
LIMIT 1
The ORDER BY clause makes it select the server with the most available quota.
OK, so I encountered just a small addition that was giving me a bug and it was that the s.server_quota had to be added to GROUP BY for it to work in the HAVING
INSERT INTO img_servers_lock(lock_server, timestamp)
SELECT s.id, NOW()
FROM alpr_servers s
LEFT JOIN img_servers_lock l ON l.lock_server = s.id
GROUP BY s.id, s.server_quota
HAVING IFNULL(COUNT(l.id), 0) < s.server_quota
ORDER BY s.server_quota - IFNULL(COUNT(l.id), 0) DESC
LIMIT 1
Thanks again Barmar!
I'm very average with MySQL, but usually I can write all the needed queries after reading documentation and searching for examples. Now, I'm in the situation where I spent 3 days re-searching and re-writing queries, but I can't get it to work the exact way I need. Here's the deal:
1st table (mpt_companies) contains companies:
| company_id | company_title |
------------------------------
| 1 | Company A |
| 2 | Company B |
2nd table (mpt_payment_methods) contains payment methods:
| payment_method_id | payment_method_title |
--------------------------------------------
| 1 | Cash |
| 2 | PayPal |
| 3 | Wire |
3rd table (mpt_payments) contains payments for each company:
| payment_id | company_id | payment_method_id | payment_amount |
----------------------------------------------------------------
| 1 | 1 | 1 | 10.00 |
| 2 | 2 | 3 | 15.00 |
| 3 | 1 | 1 | 20.00 |
| 4 | 1 | 2 | 10.00 |
I need to list each company along with many stats. One of stats is the sum of payments in each payment method. In other words, the result should be:
| company_id | company_title | payment_data |
--------------------------------------------------------
| 1 | Company A | Cash:30.00,PayPal:10.00 |
| 2 | Company B | Wire:15.00 |
Obviously, I need to:
Select all the companies;
Join payments for each company;
Join payment methods for each payment;
Calculate sum of payments in each method;
GROUP_CONCAT payment methods and sums;
Unfortunately, SUM() doesn't work with GROUP_CONCAT. Some solutions I found on this site suggest using CONCAT, but that doesn't produce the list I need. Other solutions suggest using CAST(), but maybe I do something wrong because it doesn't work too. This is the closest query I wrote, which returns each company, and unique list of payment methods used by each company, but doesn't return the sum of payments:
SELECT *,
(some other sub-queries I need...),
(SELECT GROUP_CONCAT(DISTINCT(mpt_payment_methods.payment_method_title))
FROM mpt_payments
JOIN mpt_payment_methods
ON mpt_payments.payment_method_id=mpt_payment_methods.payment_method_id
WHERE mpt_payments.company_id=mpt_companies.company_id
ORDER BY mpt_payment_methods.payment_method_title) AS payment_data
FROM mpt_companies
Then I tried:
SELECT *,
(some other sub-queries I need...),
(SELECT GROUP_CONCAT(DISTINCT(mpt_payment_methods.payment_method_title), ':', CAST(SUM(mpt_payments.payment_amount) AS CHAR))
FROM mpt_payments
JOIN mpt_payment_methods
ON mpt_payments.payment_method_id=mpt_payment_methods.payment_method_id
WHERE mpt_payments.company_id=mpt_companies.company_id
ORDER BY mpt_payment_methods.payment_method_title) AS payment_data
FROM mpt_companies
...and many other variations, but all of them either returned query errors, either didn't return/format data I need.
The closest answer I could find was MySQL one to many relationship: GROUP_CONCAT or JOIN or both? but after spending 2 hours re-writing the provided query to work with my data, I couldn't do it.
Could anyone give me a suggestion, please?
You can do that by aggregating twice. First for the sum of payments per method and company and then to concatenate the sums for each company.
SELECT x.company_id,
x.company_title,
group_concat(payment_amount_and_method) payment_data
FROM (SELECT c.company_id,
c.company_title,
concat(pm.payment_method_title, ':', sum(p.payment_amount)) payment_amount_and_method
FROM mpt_companies c
INNER JOIN mpt_payments p
ON p.company_id = c.company_id
INNER JOIN mpt_payment_methods pm
ON pm.payment_method_id = p.payment_method_id
GROUP BY c.company_id,
c.company_title,
pm.payment_method_id,
pm.payment_method_title) x
GROUP BY x.company_id,
x.company_title;
db<>fiddle
Here you go
SELECT company_id,
company_title,
GROUP_CONCAT(
CONCAT(payment_method_title, ':', payment_amount)
) AS payment_data
FROM (
SELECT c.company_id, c.company_title, pm.payment_method_id, pm.payment_method_title, SUM(p.payment_amount) AS payment_amount
FROM mpt_payments p
JOIN mpt_companies c ON p.company_id = c.company_id
JOIN mpt_payment_methods pm ON pm.payment_method_id = p.payment_method_id
GROUP BY p.company_id, p.payment_method_id
) distinct_company_payments
GROUP BY distinct_company_payments.company_id
;
I am trying to develop a ranking table for a sort of questionnaire.
Each day a question is asked at 16h (4:00 pm), which can be answered by 17:59:59 the following day. The table has to show the position of the participants taking into account the correct answers is the time.
My table will be of the sort:
+-------+---------+---------------------+
|userid | correct | timestamp |
+-------+---------+---------------------+
| 2 | 1 | 2018-02-07 16:00:01 |
| 1 | 1 | 2018-02-07 16:02:00 |
| 3 | 1 | 2018-02-07 17:00:00 |
| 1 | 0 | 2018-02-08 16:00:02 |
| 3 | 1 | 2018-02-08 16:00:05 |
| 2 | 0 | 2018-02-08 16:01:00 |
+-------+---------+---------------------+
For now I started with this query:
SELECT `userid`, `correct `, `timestamp`,
count(correct) as count
FROM `results`
WHERE correct = 1
GROUP BY `userid `
ORDER BY count DESC, timestamp DESC
But I have already realized that this is not what I intend because the ranking has to be cumulative but taking into account the several days.
Does anyone have an idea how I can do this?
A user from Stackoverflow Portugal advised this code but it is not working either.
SELECT userid, SUM(correct),
SUM(TIMESTAMPDIFF(HOUR,(timestamp,CAST(CONCAT_WS(' ',date(timestamp), '17:59:59') as DATETIME)))) time
FROM results
GROUP BY userid
ORDER BY correct DESC, time
Don’t deal with this datetime (16h), this may be changed and you will be lost on your query.
Instead, you should count by userid and questionnaire_id. To do so:
add new table questionnaire [id, title] (you can add extra column
later : created_time, end_time, …)
edit your record table by adding the questionnaire id as FK : [userid, questionnaireid, correct, timestamp]
then count normally: Correct answer by user, by questionnaire
SELECT userid, questionnaireid ,
sum(correct) as total
FROM results r
INNER JOIN questionnaire q
ON r.questionnaireid = q.id
WHERE correct = 1
GROUP BY userid, questionnaireid
ORDER BY total DESC, id ASC
I am unable to map the record as my expectation.
Doc Table
-------+-------------------
doc_id | doc_title
-------+-------------------
1 | My book
-------+-------------------
2 | My sec Book
--------------------------
Doc details Table
-----------+--------------+-----------------------
fk_doc_id | doc_version | submit_date
-----------+--------------+-----------------------
1 | 1 | 2015-10-25 14:32:01
-----------+--------------+-----------------------
1 | 2 | 2015-10-26 13:00:01
-----------+--------------+-----------------------
1 | 3 | 2015-10-27 09:00:00
--------------------------+-----------------------
2 | 1 | 2015-10-25 11:15:01
-----------+--------------+-----------------------
2 | 2 | 2015-10-26 10:00:00
--------------------------+-----------------------
Question: How do I join this two tables to get each documents with the latest version doc info? even though I get the latest version but the row info which is not correct.
So far I have tried this query
SELECT *, max(doc_version) AS latest_version
FROM d_doc
JOIN d_doc_dtl ON d_doc.doc_id = d_doc_dtl.fk_doc_id
GROUP BY d_doc.doc_id;
My expected result is
--------+--------------+----------------+--------------------
doc_id | doc_title | latest_version | submit_date
--------+--------------+----------------+--------------------
1 | My book | 3 | 2015-10-27 09:00:00
--------+--------------+----------------+--------------------
2 | My sec book | 2 | 2015-10-26 10:00:00
----------------------------------------+--------------------
but my result is
--------+--------------+----------------+--------------------
doc_id | doc_title | latest_version | submit_date
--------+--------------+----------------+--------------------
1 | My book | 3 | 2015-10-25 14:32:01
--------+--------------+----------------+--------------------
2 | My sec book | 2 | 2015-10-25 11:15:01
----------------------------------------+--------------------
NOTE: the submit_date which is no correct.
SELECT d_doc.doc_id, d_doc.doc_title, max_table.latest_version
FROM d_doc JOIN (
select fk_doc_id, max(doc_version) as latest_version from d_doc_dtl group by fk_doc_id
) as max_table ON d_doc.doc_id = max_table.fk_doc_id
This query should work as you expect. It selects latest document versions in inner subquery and than joins it with documents.
SELECT d.doc_id,
d.doc_title,
dtl.doc_version latest_version,
dtl.submit_date
FROM d_doc d
INNER JOIN (SELECT dt.*
FROM d_doc_dtl dt
INNER JOIN (SELECT fk_doc_id, MAX(doc_version) doc_version
FROM d_doc_dtl
GROUP BY fk_doc_id) dm
ON dt.fk_doc_id = dm.fk_doc_id
AND dt.doc_version = dm.doc_version) dtl
ON d.doc_id = dtl.fk_doc_id
You get wrong results because you selected only max(version), but date as it is not in group by clause can contain any value. First you need to get records containing latest version as shown above.
Easy, instead of
SELECT *, max(doc_version) AS latest_version
Use this
SELECT d_doc.*, max(doc_version) AS latest_version
What you were doing by selecting * is getting all the results after the table is joined and you only wanted the original table results.
select * from doc_table , doc_version where exists( select
max(version_id)
from
doc_version vert
where
(doc_table .DOC_ID = vert.VERSION_DOC_ID) ) group by doc_id;
You can try something like this.