► Context : I work in a museum (for real), people come everyday, they buy tickets for themselves (humans) and sometimes they also buy tickets for drinks and foods (objects). There are events, tickets have the same names per event but the prices are different.
► The problem : I have to create a report with 2 results : total sales (visitors + food + drinks) and how many people came (visitors only) for a specific event. Next is an image of the 3 tables in the database, how they relate and some sample data :
Table TICKETS relates to SALES_MAIN through EVENT_ID column.
Table SALES_MAIN relates to SALES_DETAIL through ID→MAIN_ID columns.
Table SALES_DETAIL have a column TICKET_NAME but it's not unique in table TICKETS.
► The question : How to get both results, total sales and human count, for event 555 in one "select" ? I tried next 2 "select" but when I combine them with another INNER JOIN I get cartesian results :
Get detail sales for event 555 :
SELECT sales_detail.* FROM sales_main
INNER JOIN sales_detail ON sales_detail.main_id = sales_main.id
WHERE sales_main.event_id = '555'
Get tickets for event 555 :
SELECT * FROM tickets WHERE tickets.event_id = '555'
Use:
SELECT
SUM(CASE WHEN sd.ticket_name IN ('adult', 'child') THEN sd.quantity
ELSE 0 END) AS total_visitors,
SUM(sd.quantity * t.price) AS total_sales
FROM sales_main sm
JOIN sales_detail sd
ON sd.main_id = sm.id
JOIN ticket t
ON t.event_id = sm.event_id
AND t.ticket_name = sd.ticket_name
WHERE sm.event_id = '555';
Conditional aggregation could also be based on type:
SUM(CASE WHEN t.ticket_type ='human' THEN sd.quantity ELSE 0 END)
Related
I have a table of ads and another of prices, relative to these ads. When the user modifies a price in the application, a new row is added to the prices table, which includes the price and the date the price was modified.
I need to get a list with all the ads and, for each ad, the first price that was registered, and also the last. I have used a double subquery on the same price table.
Tables
ads
id int
ad_title varchar
1
Potatoes
2
Tomatoes
prices
id int
price decimal
price_timestamp timestamp
ads_id int
1
50
2021-02-16 21:12:36
1
2
5
2021-02-17 21:12:48
1
3
1000
2021-02-17 21:20:40
2
4
900
2021-02-18 13:20:49
2
5
700
2021-02-18 13:20:49
2
Query
SELECT ads.ad_title, prices_firsts.price AS price_first, prices_currents.price AS price_current
FROM ads
LEFT JOIN (
SELECT *
FROM prices
GROUP BY id
ORDER BY price_timestamp ASC
) prices_firsts ON prices_firsts.ads_id = ads.id
LEFT JOIN (
SELECT *
FROM prices
GROUP BY id
ORDER BY price_timestamp DESC
) prices_currents ON prices_currents.ads_id = ads.id
GROUP BY ads.id
Esta consulta devuelve lo siguiente en mi servidor local (XAMPP):
ad_title
price_first
price_current
Potatoes
50
5
Tomatoes
1000
700
As you can see the result of the query is correct BUT when it is executed on a server from an external provider (I have tested it in 1&1 IONOS and in Arsys Spain) the results vary practically with each execution. There are times when prices appear in reverse order, sometimes the same price appears in both columns...
What I need?
I need to understand if the problem is in the servers configuration of these providers or if the query is wrong.
I am also open to ideas that you can contribute to get the prices (first and current) in another way, even if it is with another structure in the database.
You could also try using a subquery for min and max date
select ads.id, p1.price min_price, p2.price max_price,
from ads
inner join (
select ads_id, min(price_timestamp ) min_date, max(price_timestamp ) max_date
from prices
group by ads_id
) t on t.ads_id = ads.id
INNER JOIN prices p1 on p1.ads_id = ads.id and p1.price_timestamp = t.min_date
INNER JOIN prices p2 on p2.ads_id = ads.id and p2.price_timestamp = t.mmaxn_date
I have 2 tables, customer and tickets
I want to be able to select from the tickets table and order by:
tickets.priority then customer.category_priority then tickets.queue_time
I have this query:
SELECT t.*
from tickets t
JOIN customer c ON t.company = c.sequence
WHERE t.status <> 'Completed' AND t.queue = 'Y'
ORDER BY field(t.priority, 'Critical', 'High', 'Medium', 'Low'), t.queue_time ASC
which works great for the tickets.priority and tickets.queue_time
but im not sure how to incorporate the customer.category_priority
so in the customer table, i have columns with names like:
priority_computers
priority_telephone
priority_software
all INT fields and have a value of 0, 1 or 2
the row in tickets has a category column which is either Computers, Telephone, or Software and thats what needs to link to the above.
so, if a customer row has priority_computers of 2 and the tickets row is category = 'Computers' that would be at the top of the list because the customer record has the priority of 2 and it would also incorporate the other ORDER BY conditions
Examples:
Customers:
Company A priority_computers = 1
Company B priority_computers = 2
Company C priority_computers = 3
Example One:
Ticket 1 Company A priority = Medium category = Computers
queue_time = 2015-11-20 08:00
Ticket 2 Company B priority = Medium category = Computers
queue_time = 2015-11-20 10:00:00
Ticket 3 Company C priority = Medium category = Computers
queue_time = 2015-11-20 08:30:00
This should output in the following order:
Ticket 3
Ticket 2
Ticket 1
Example 2:
Ticket 1 Company B priority = High category = Computers
queue_time = 2015-11-20 12:00
Ticket 2 Company A priority = Medium category = Computers
queue_time = 2015-11-20 07:00:00
Ticket 3 Company C priority = Medium category = Computers
queue_time = 2015-11-20 07:00:00
This should output in the following order:
Ticket 1
Ticket 3
Ticket 2
If I understand this correctly, then your problem is that you have to match data with column names somehow.
customer.priority_computers for ticket.category = 'Computers'
customer.priority_telephone for ticket.category = 'Telephone'
customer.priority_software for ticket.category = 'Software'
This shows a database design flaw. There should be a customer_priority table instead with each row holding a customer, a category and the associated value. Then you could simply join.
As is, you must check data content and decide for a column to use in your query:
SELECT t.*
from tickets t
JOIN customer c ON t.company = c.sequence
WHERE t.status <> 'Completed' AND t.queue = 'Y'
ORDER BY field(t.priority, 'Critical', 'High', 'Medium', 'Low')
, case t.category
when 'Computers' then c.priority_computers
when 'Telephone' then c.priority_telephone
when 'Software' then c.priority_software
end
, t.queue_time ASC
Update: Here is a query you'd write, if you had a customer_priority table. You see, your query doesn't need to know what categories exist and how to treat them any longer.
SELECT t.*
from tickets t
JOIN customer c ON t.company = c.sequence
JOIN customer_priority cp ON cp.customer_id = c.sequence
AND cp.category = t.category
WHERE t.status <> 'Completed' AND t.queue = 'Y'
ORDER BY field(t.priority, 'Critical', 'High', 'Medium', 'Low')
, cp.priority_value
, t.queue_time ASC
Moreover: As mentioned, it is strange to have a table customer, but in tickets it's not called a customer, but a company, and in the customer table itself it's not called a customer number or ID either, but a sequence. This makes the queries less readable. I suggest, you change the names, if possible, so they are consistent.
Also your query shouldn't have to know what 'Critical' and 'High' means. There should be a table for priorities where each priority has a name and a value, so the query could simply pick the value and work with it without having to know anything else about the priorities.
I have the following tables, in a standard shop:
(id is always primary key, auto-increment, ts is always type TIMESTAMP, updated ON_UPDATE CURRENT_TIMESTAMP)
table sales:
id | total | tendered | flag | userID | ts
1 0.6 0.6 0 4 2013-11-21 08:12:23
Sales is the parent table, userID is related to the user that made the sale. total and tendered are both of type FLOAT. flag is of type VARCHAR and could be Free Order.
table receipts:
id | oID | pID | quantity | ts
1 1 26 1 2013-11-21 08:11:25
Receipts holds a line for each unique type of product sold. oID is type INT and relates to the id of table sales. pID is of type INT and relates to the id of table products.
table products:
id | name | price | cID | display | ts
1 Mars 0.6 3 1 2014-01-17 07:55:25
Products is the central data for each product in the database. Here is a line for mars bars. cID relates to the id in table categories.
table categories
id | name | display | ts
3 Snacks 1 2013-11-14 12:06:44
Categories is the table holding all the data about each category, and can have multiple products relating to a single row. display is of type INT and dictates when the category is enabled or disabled (1 = 'true')
My question is, I want to output information like this:
**Snacks**
(name) (quantity) (price) (total)
Fruit 3 50p £1.50
Twix 1 60p 60p
Boost 1 60 60p
**Hot Drinks**
(name) (quantity) (price) (total)
English Tea 15 60p £9.00
Speciality Teas 2 60p £1.20
Which I have the following SQL for:
SELECT categories.name AS category, products.name, pID,
(SELECT SUM(quantity) FROM receipts WHERE pID=r.pID AND DATE(ts) = CURDATE()) AS quantity,
products.price,r.ts
FROM receipts r
LEFT JOIN products ON r.pID = products.id
LEFT JOIN categories ON products.cID = categories.id
WHERE DATE(r.ts) = CURDATE()
GROUP BY r.pID
ORDER BY categories.name;
Which seems to give me the correct information, but I am not 100% certain. If anyone could verify that this works, I would be most grateful. But when I want to see a particular day, I get unusual figures with the following SQL:
$postfrom = $_POST['from_mm']."/".$_POST['from_dd']."/20".$_POST['from_yy'];
$postto = $_POST['to_mm']."/".$_POST['to_dd']."/20".$_POST['to_yy'];
$from = strtotime($postfrom . " 6:00");
$to = strtotime($postto . " 23:59");
$itemised = select("SELECT categories.name AS category, products.name, pID,
(SELECT SUM(quantity) FROM receipts WHERE pID = r.pID AND UNIX_TIMESTAMP(r.ts) > '{$from}' AND UNIX_TIMESTAMP(r.ts) < '{$to}')
AS quantity, products.price
FROM receipts r
LEFT JOIN products ON r.pID = products.id
LEFT JOIN categories ON products.cID = categories.id
WHERE UNIX_TIMESTAMP(r.ts) > '{$from}'
AND UNIX_TIMESTAMP(r.ts) < '{$to}'
GROUP BY r.pID
ORDER BY categories.name;");
(function 'select' simply returns an array of the SQL table). The thing is, I could find the results easily by looping through in PHP and adding it up that way. But I know this is possible with SQL, I just don't know why It isnt working. Can somebody please help?
Edit SQL sample fiddle is here: http://sqlfiddle.com/#!2/23af4 although I couldn't do more than half a day of data due to 8000 character restrictions.
Try this:
SELECT categories.name AS category, products.name AS name,
receipts.quantity AS quantity, products.price AS price,
(receipts.quantity * products.price) AS total
FROM categories
JOIN products
ON categories.id = products.cID
JOIN receipts
ON receipts.pID = products.ID
WHERE DATE(receipts.ts) = CURDATE()
ORDER BY categories.name
SQLFiddle demo
With regard to the date restriction, you could use BETWEEN ... AND ... to specify the date and time. Using an absolute date and time moment or relative to the current day and time, for example WHERE DATE(receipts.ts) BETWEEN concat(curdate() -5,' 6:00:00 AM') AND curdate() -4
I have a database table where I am storing all the values from an external xml file. Since my project requirement demands me to deal with this unnormalized data. I need to help to extract data in an appropriate way.
I have two web pages (one for categories) and one for products.My database table looks like this:
**product_id Code Name ProductRange ProductSubRange WebCategory**
1 1002 Lid 30l Crystal;Uni LIDs Household products
2 10433 Casa Basket Silver Casa Casa Hipster BASKET Kitchenware
3 17443 Casa Basket Ice White Casa;Laundry LAUNDRY BASKET Laundry products
4 13443 Garden tub Eden Eden Garden Pots Household products
5 199990 Black Lid 201 Crystal Crystal Lids Household products
The product that belong to more than one productRange is indicated my semicolon(;). For example,above product_id 1 with name "Lid 301" belongs to two Product Ranges "Crystal" and "Uni". Same is for product_id 3. However product 2 belongs to single ProductRange.
MY QUESTIONs:
1) How can I construct a query so that it could return "ProductRange" based on my query_string values of "Webcategory"? For example:
if I get "Household Products" as my WebCategory from query string, it could give me distinct like this:
Household Products
|-Crystal
|-Uni
|-Eden
Laundry Products
|-Casa
|-Laundry
Kitchenware
|-Casa
2) Based on extracted ProductRanges, I want to display products separately in my webpages according to the product range and webcategory. For example, if I choose "Crystal" from above, it could give me Products with product_id "1" and "5" respectively like this:
Household Products|
|-Crystal
|-Lid 301 (product_id=1)
|-Balck Lid 201 (product_id=5)
|-Uni
|-Lid 301 (product_id=1)
|-Eden
|-Garden Tub
Kitchenware|
|-Casa
|-Casa Basket silver
Laundry Products|
|-Casa
|-Casa Basket Ice White
|
|-Laundry
|-Casa Basket Ice White
Can anyone guide me how can I retrieve records from the database and what I will need to do as I am new to programming? I would appreciate if anyone could help me in this regard.
In order to get distinct product ranges based on a give WebCategory input = 'XYZ', you can use the following - don't be intimidated by the numberstable, it's just a helpful table that contains rows each with increasing integer values from 1 ... up to N where N is the maximum number of characters in your ProductRange column. These can be made by hand or using a special insert/select query like the one found here:
http://www.experts-exchange.com/Database/MySQL/A_3573-A-MySQL-Tidbit-Quick-Numbers-Table-Generation.html
SELECT DISTINCT
SUBSTRING(ProductRange FROM number FOR LOCATE(';', ProductRange, number) - number) AS ProductRange
FROM (
SELECT ProductRange, CASE number WHEN 1 THEN 1 ELSE number + 1 END number
FROM (
SELECT mydatabasetable.ProductRange, numberstable.number
FROM mydatabasetable
INNER JOIN numberstable
ON numberstable.number >= 1
AND numberstable.number <= CHAR_LENGTH(mydatabasetable.ProductRange)
WHERE WebCategory = 'XYZ'
) TT
WHERE number = 1 OR (number + 1) <= CHAR_LENGTH(ProductRange)
) TT
WHERE SUBSTRING(ProductRange FROM number FOR 1) = ';'
OR numberstable.number = 1;
In order to retrieve a result set with all values WebCategory, ProductRange and Product for your website you can use the below slightly modified version derived from the above query. So that the results will appear more meaningful at first, I added an ORDER BY clause to keep all same-category, same-product-range products in sequence one after the other. This might or might not be desired as you might prefer to do that in your application/server-script code. In that case you can remove the ORDER BY clause without doing any harm.
SELECT WebCategory,
SUBSTRING(
ProductRange
FROM number
FOR LOCATE(';', ProductRange, number) - number
) AS ProductRange,
Product
FROM (
SELECT WebCategory, ProductRange, Product,
CASE number
WHEN 1 THEN 1
ELSE number + 1
END number
FROM (
SELECT WebCategory, ProductRange, Product, numberstable.number
FROM mydatabasetable
INNER JOIN numberstable
ON numberstable.number >= 1
AND numberstable.number <= CHAR_LENGTH(ProductRange)
) TT
WHERE number = 1 OR (number + 1) <= CHAR_LENGTH(ProductRange)
) TT
WHERE SUBSTRING(ProductRange FROM number FOR 1) = ';'
OR numberstable.number = 1
ORDER BY WebCategory, ProductRange, Product
You are probably going to want to do a GROUP BY clause in your query and maybe an JOIN if the detailed data is in a different table. If I understand you correctly it would look something like this.
SELECT T.WebCategory, T.ProductRange, T2.Product FROM table T
INNER JOIN table2 T2 ON T2.ProductRange = T.ProductRange
WHERE T.WebCategory = 'Household products'
GROUP BY T.WebCategory, T.ProductRange, T2.Product
It is tough to test my query without having a database setup to test against, but something like the above should return what you are looking for. You will of course need to rename your columns based on the actual names in the second table. Overall though, this should get you started if I understood the question correctly.
I've tried everything to figure this out but I can't get the correct total. My attempts either add all the records and not just the latest ones or I only get the first record.
My first table: hubs
hubID hubName
1 hub1
2 hub2
My second table: hub_reports
reportID hubID date health school
1 1 2012-04-27 467 322
2 2 2012-04-23 267 22
3 1 2012-01-20 176 623
So what you see is 2 tables, one with the organizations name and other info and the second with the reports that each organization submits quarterly. I want to list all the organizations and their latest report. At the bottom of the table I want to add all the available health kits and school kits that are currently available.
Here's the code I'm using right now to display all the organizations and their latest reports.
SELECT * FROM (SELECT hubName, date, health, school FROM hub_reports,
hubs WHERE hub_reports.hubID = hubs.hubID ORDER BY date DESC) AS Total
GROUP BY hubName
This seems to work but when I try the same tactic to get the SUM of the health and school columns I don't get the right answer.
SELECT SUM(health) FROM (SELECT hubName, date, health FROM
hub_reports, hubs WHERE hub_reports.hubID = hubs.hubID ORDER BY date
DESC) AS Total GROUP BY hubName
I tried other using a LEFT JOIN approach that I found on another forum but it didn't seem to work any better. But I maybe I wasn't doing it right.
Please help!
I just encountered a similar problem in a project of mine. A variation of this query worked for me. Hope it is helpful to you.
SELECT hubs.hubName, hub_reports.*,
SUM(hub_reports.health) AS ttl_health,
SUM(hub_reports.school) AS ttl_school
FROM hubs, hub_reports
WHERE hub_reports.hubID = hubs.hubID
GROUP BY hub_reports.hubID
ORDER BY hub_reports.date DESC
Here's the PHP:
$rs = mysql_query( 'SELECT hubs.hubName, hub_reports.*,
SUM(hub_reports.health) AS ttl_health,
SUM(hub_reports.school) AS ttl_school
FROM hubs, hub_reports
WHERE hub_reports.hubID = hubs.hubID
GROUP BY hub_reports.hubID
ORDER BY hub_reports.date DESC' );
$grand_total['school']=0;
$grand_total['health']=0;
while ( $row = mysql_fetch_assoc( $rs ) ){ // Step through each hub
echo "{$row['hubName']} shows {$row['ttl_school']} total school, {$row['ttl_health']} total health";
$grand_total['school'] += $row['ttl_school'];
$grand_total['health'] += $row['ttl_health'];
}
echo "Grand Total School: {$grand_total['school']}, Grand Total Health: {$grand_total['health']}";
You're likely looking for the MAX() function.
Try this:
SELECT h.hubID, h.hubname, MAX(hr.date) as hrdate, SUM(hr.health) as health, SUM(hr.school) as school
FROM hubs h
LEFT JOIN hub_reports hr ON hr.hubID = h.hubID
GROUP BY h.hubID
Edit
You want the MAX date so it only returns the most recent entry (assuming your entries are entered by date, of course).