How to get the price for the first service date and last service date - plsqldeveloper

DECLARE
v_in auto_service.vin%TYPE;
v_first auto_service.service_date%TYPE;
v_last auto_service.service_date%TYPE;
v_max auto_service.price%TYPE;
v_total auto_service.price%TYPE;
v_n NUMBER;
CURSOR c_auto
IS
SELECT vin,
COUNT(*) AS no,
MIN(SERVICE_DATE) AS FIRSTprice,
MAX(SERVICE_DATE) AS lastprice,
max(price) as maxprice,
sum(price) as totalprice
FROM auto_service
GROUP BY vin;
BEGIN
OPEN c_auto;
FETCH c_auto INTO v_in,v_n,v_first,v_last,v_total,v_max;
IF c_auto%notfound THEN
dbms_output.put_line('No output');
ELSE
dbms_output.put_line('vin No firstprice lastprice maximumprice totalprice');
LOOP
dbms_output.put_line(rpad(v_in,10) || rpad(v_n,10) || rpad(v_first,10) || rpad(v_last,12) || rpad(v_max,15) || rpad(v_total,5));
FETCH c_auto INTO v_in,v_n,v_first,v_last,v_max,v_total;
EXIT
WHEN c_auto%notfound;
END LOOP;
END IF;
CLOSE c_auto;
END;
To find
the number of services, the first service date and the price for the first service,the last service date and the price for the last service, the maximum price and the service date for the maximum price, and the prices for all services
I got all the other things except price for the first service date and last date of all VIN.

select q.*,
(
select sum(price)
from auto_service x
where x.vin = q.vin
and x.service_date = q.FIRST_DATE
) as FIRST_PRICE,
(
select sum(price)
from auto_service x
where x.vin = q.vin
and x.service_date = q.LAST_DATE
) AS LAST_PRICE
from
(
SELECT vin,
COUNT(*) AS no,
MIN(SERVICE_DATE) AS FIRST_DATE,
MAX(SERVICE_DATE) AS LAST_DATE,
max(price) as maxprice,
sum(price) as totalprice
FROM auto_service
GROUP BY vin
) q
Notice that I renamed the columns you named as FIRSTprice and lastprice to first_date and last_date, since this name is more related to what the column does actually contain.
in second place: i used "select sum(price)" in the subqueries only to handle the possibility that the same car has been serviced twice on the same day. if this should happen, without the sum(), the subquery would extract more than a value and would give you a runtime error. This much less likely to happen if if the date field contains also the time part, not only the date, but it could still happen if your DB contains bad data.
it is up to you if you want to keep that sum() call or if you prefer the db to reveal errors if there are duplicate rows you don't expect

Related

Generating complex sql tables

I currently have an employee logging sql table that has 3 columns
fromState: String,
toState: String,
timestamp: DateTime
fromState is either In or Out. In means employee came in and Out means employee went out. Each row can only transition from In to Out or Out to In.
I'd like to generate a temporary table in sql to keep track during a given hour (hour by hour), how many employees are there in the company. Aka, resulting table has columns HourBucket, NumEmployees.
In non-SQL code I can do this by initializing the numEmployees as 0 and go through the table row by row (sorted by timestamp) and add (employee came in) or subtract (went out) to numEmployees (bucketed by timestamp hour).
I'm clueless as how to do this in SQL. Any clues?
Use a COUNT ... GROUP BY query. Can't see what you're using toState from your description though! Also, assuming you have an employeeID field.
E.g.
SELECT fromState AS 'Status', COUNT(*) AS 'Number'
FROM StaffinBuildingTable
INNER JOIN (SELECT employeeID AS 'empID', MAX(timestamp) AS 'latest' FROM StaffinBuildingTable GROUP BY employeeID) AS LastEntry ON StaffinBuildingTable.employeeID = LastEntry.empID
GROUP BY fromState
The LastEntry subquery will produce a list of employeeIDs limited to the last timestamp for each employee.
The INNER JOIN will limit the main table to just the employeeIDs that match both sides.
The outer GROUP BY produces the count.
SELECT HOUR(SBT.timestamp) AS 'Hour', SBT.fromState AS 'Status', COUNT(*) AS 'Number'
FROM StaffinBuildingTable AS SBT
INNER JOIN (
SELECT SBIJ.employeeID AS 'empID', MAX(timestamp) AS 'latest'
FROM StaffinBuildingTable AS SBIJ
WHERE DATE(SBIJ.timestamp) = CURDATE()
GROUP BY SBIJ.employeeID) AS LastEntry ON SBT.employeeID = LastEntry.empID
GROUP BY SBT.fromState, HOUR(SBT.timestamp)
Replace CURDATE() with whatever date you are interested in.
Note this is non-optimal as it calculates the HOUR twice - once for the data and once for the group.
Again you are using the INNER JOIN to limit the number of returned row, this time to the last timestamp on a given day.
To me your description of the FromState and ToState seem the wrong way round, I'd expect to doing this based on the ToState. But assuming I'm wrong on that the following should point you in the right direction:
First, I create a "Numbers" table containing 24 rows one for each hour of the day:
create table tblHours
(Number int);
insert into tblHours values
(0),(1),(2),(3),(4),(5),(6),(7),
(8),(9),(10),(11),(12),(13),(14),(15),
(16),(17),(18),(19),(20),(21),(22),(23);
Then for each date in your employee logging table, I create a row in another new table to contain your counts:
create table tblDailyHours
(
HourBucket datetime,
NumEmployees int
);
insert into tblDailyHours (HourBucket, NumEmployees)
select distinct
date_add(date(t.timeStamp), interval h.Number HOUR) as HourBucket,
0 as NumEmployees
from
tblEmployeeLogging t
CROSS JOIN tblHours h;
Then I update this table to contain all the relevant counts:
update tblDailyHours h
join
(select
h2.HourBucket,
sum(case when el.fromState = 'In' then 1 else -1 end) as cnt
from
tblDailyHours h2
join tblEmployeeLogging el on
h2.HourBucket >= el.timeStamp
group by h2.HourBucket
) cnt ON
h.HourBucket = cnt.HourBucket
set NumEmployees = cnt.cnt;
You can now retrieve the counts with
select *
from tblDailyHours
order by HourBucket;
The counts give the number on site at each of the times displayed, if you want during the hour in question, we'd need to tweak this a little.
There is a working version of this code (using not very realistic data in the logging table) here: rextester.com/DYOR23344
Original Answer (Based on a single over all count)
If you're happy to search over all rows, and want the current "head count" you can use this:
select
sum(case when t.FromState = 'In' then 1 else -1) as Heads
from
MyTable t
But if you know that there will always be no-one there at midnight, you can add a where clause to prevent it looking at more rows than it needs to:
where
date(t.timestamp) = curdate()
Again, on the assumption that the head count reaches zero at midnight, you can generalise that method to get a headcount at any time as follows:
where
date(t.timestamp) = "CENSUS DATE" AND
t.timestamp <= "CENSUS DATETIME"
Obviously you'd need to replace my quoted strings with code which returned the date and datetime of interest. If the headcount doesn't return to zero at midnight, you can achieve the same by removing the first line of the where clause.

How to return zero values if nothing was written in time interval?

I am using the Graph Reports for the select below. The MySQL database only has the active records in the database, so if no records are in the database from X hours till Y hours that select does not return anything. So in my case, I need that select return Paypal zero values as well even the no activity was in the database. And I do not understand how to use the UNION function or re-create select in order to get the zero values if nothing was recorded in the database in time interval. Could you please help?
select STR_TO_DATE ( DATE_FORMAT(`acctstarttime`,'%y-%m-%d %H'),'%y-%m-%d %H')
as '#date', count(*) as `Active Paid Accounts`
from radacct_history where `paymentmethod` = 'PayPal'
group by DATE_FORMAT(`#date`,'%y-%m-%d %H')
When I run the select the output is:
Current Output
But I need if there are no values between 2016-07-27 07:00:00 and 2016-07-28 11:00:00, then in every hour it should show zero active accounts Like that:
Needed output with no values every hour
I have created such select below , but it not put to every hour the zero value like i need. showing the big gap between the 12 Sep and 13 Sep anyway, but there should be the zero values every hour
(select STR_TO_DATE ( DATE_FORMAT(acctstarttime,'%y-%m-%d %H'),'%y-%m-%d %H')
as '#date', count(paymentmethod) as Active Paid Accounts
from radacct_history where paymentmethod <> 'PayPal'
group by DATE_FORMAT(#date,'%y-%m-%d %H'))
union ALL
(select STR_TO_DATE ( DATE_FORMAT(acctstarttime,'%y-%m-%d %H'),'%y-%m-%d %H')
as '#date', 0 as Active Paid Accounts
from radacct_history where paymentmethod <> 'PayPal'
group by DATE_FORMAT(#date,'%y-%m-%d %H')) ;
I guess, you want to return 0 if there is no matching rows in MySQL. Here is an example:
(SELECT Col1,Col2,Col3 FROM ExampleTable WHERE ID='1234')
UNION (SELECT 'Def Val' AS Col1,'none' AS Col2,'' AS Col3) LIMIT 1;
Updated the post: You are trying to retrieve data that aren't present in the table, I guess in reference to the output provided. So in this case, you have to maintain a date table to show the date that aren't in the table. Please refer to this and it's little bit tricky - SQL query that returns all dates not used in a table
You need an artificial table with all necessary time intervals. E.g. if you need daily data create a table and add all day dates e.g. start from 1970 till 2100.
Then you can use the table and LEFT JOIN your radacct_history. So for each desired interval you will have group item (group by should be based on the intervals table.

Mysql returns null for rows that doesn't exist

I have the following code:
while ($row = mysql_fetch_array($result)){
$que ='select SUM(price) from prices_adverts where advert_id="7" and room_type_id="54" and (date >= "2013-09-20" AND date <"2013-09-21") order by price';
$que ='select SUM(price) from prices_adverts where advert_id="7" and room_type_id="55" and (date >= "2013-09-20" AND date <"2013-09-21") order by price'; and etc
$res=mysql_query($que) or die();
$rw=mysql_fetch_row($res);
$price= $rw['0'];
}
this returns sum for some records that have prices in the database and NULL for $price for the records dont exist /when a room doesnt has price for specific dates it doesn't exist in the table /
So my question is how I can get result for records that exist only??? I do not need NULL values for prices and is it possible to access $price outside while ? How? Please help, thanks
May I explain what exactly I need, this may help you to help Me :)
Above I am looping hotels rooms to check how much would cost the room for specific period. Than I need to draw button outside loop which will divert visitor to reservation page. But if a hotel has no room prices available for the dates, I wish to have no button for reservation. That's why I need to figure out is there at least 1 room with prices in the hotel or not.. Hope this helps
########################################################Update
first query: I am taking all London hotels id-s
select id from adverts where town="London" limit 0, 5
than
for($i=0;$i<$num_rows;$i++){
$row=mysql_fetch_row($result);
echo echo_multy_htl_results($row[0]);
}
this function echo_multy_htl_results is:
select a.article_title, a.town, a.small_image, a.plain_text, a.star_rating, a.numberrooms, rta.room_type_id, rt.bg_room_type,a.longitude, a.latitude, a.link_name, a.id from adverts a, rooms_to_adverts rta,room_types rt where a.id = rta.advert_id and rta.advert_id="3" and rta.room_type_id=rt.id and rt.occupants>="1" group by rt.bg_room_type order by rt.occupants ASC
it gets info for the html hotel square and also room_types_id-s and that it comes the cod already added.. What would you suggest ?
Maybe by adding AND price IS NOT NULL ?
The solution to the immediate problem at hand can be this query:
select SUM(price)
from prices_adverts
where advert_id="7"
and room_type_id="54" -- notice, we are filtering on room type here
and (date >= "2013-09-20" AND date <"2013-09-21")
group by room_type_id -- this makes no rows appear when there are no rows found in this case
order by price
It returns 1 row, when there were a corresponding rows, and 0 rows, when there were none.
However, your problem seems to be of a different nature. Your scheme of operation seems to be like this:
query rows from the DB (room_type_ids)
put them in a loop
for each iteration run a query
This is bad. Databases are very good at solving these kinds of problems, using JOINs, and the other appropriate clauses. I'd suggest using these features, and turning things around in your head. That way, you could issue one query returning all data you need. I believe this might be such a query, providing all the room type IDs with their summed prices:
select room_type_id, SUM(price)
from prices_adverts
where advert_id="7" -- notice: no filtering for room_type_id this time
and (date >= "2013-09-20" AND date <"2013-09-21")
group by room_type_id
order by price
This query lists all room_type_ids that have records, and does not list those that don't, and beside each different type_id, it has the summed price. You can see the results in this SQL fiddle. (the data types are obviously off, this is just to show it in operation)
EDIT
To have the advert IDs similar to the room_type_ids too:
select advert_id, room_type_id, SUM(price)
from prices_adverts
where (date >= "2013-09-20" AND date <"2013-09-21")
-- notice: no filtering for room_type_id or advert id this time
group by advert_id, room_type_id
order by price
This will have three columns: advert_id, room_type_id and the summed price...
You could use
sum(case when price is null then 0 else price end)
or
sum(isnull(price,0))
or
just add in your where clause `price is not null` to exclude them.
You need to use HAVING
select SUM(price)
from prices_adverts
where advert_id="7" and room_type_id="54" and (date >= "2013-09-20" AND date <"2013-09-21")
having sum(price) is not null
order by sum(price)

MySql retrieve products and prices

I would like to retrieve a list of all the products, with their associated prices for a given period.
The Product table:
Id
Name
Description
The Price table:
Id
Product_id
Name
Amount
Start
End
Duration
The most important thing to not here, is that a Product can have mutliple prices, even over the same period, but not with the same duration.
For example, a price from "2013-06-01 -> 2013-06-08" and another from "2013-06-01 -> 2013-06-05"
So my aim is to retrieve, for a given period, the lists of all products, paginated by 10 product for example, joined to the prices existant over the period.
The basic way to do so would be:
SElECT *
FROM product
LEFT JOIN prices ON ...
WHERE prices.start >= XXX And prices.end <= YYY
LIMIT 0,10
The problem while using this simple solution, is that I can't retrieve only 10 Products, but 10 Products*Prices, which is not acceptable in my case.
So the solution would be:
SElECT *
FROM product
LEFT JOIN prices ON ...
WHERE prices.start >= XXX And prices.end <= YYY
GROUP BY product.id
LIMIT 0,10
But the problem here is, i'll only retrieve "1" price for each product.
So I wonder what would be the best way to handle this.
I could for example use a group function, like "group_concat", and retrieve in a field all the prices in a string, like "200/300/100" and so on. That seem weird, and would need work on server-language side to transform to a readable information, but it could work.
Another solution would be to use different column for each prices, depending on duration:
SELECT
IF( NOT ISNULL(price.start) AND price.duration = 1, price.amount , NULL) AS price_1_day
---- same here for all possible durations ---
From ...
Thta would work too i guess (i'm not really sure if this is possible however), but I may need to create about 250 columns to cover all possibilities. Is that a safe option ?
Any help will be much appreciated
I believe that a group_concat would be the best way forward on this, as its very purpose is to aggregate multiple pieces of data relating to a particular column.
However, adapting on peterm's SQL fiddle, this is possible to do in 1 query if using user defined variables. (If one ignores the initial query for setting the vars)
http://dev.mysql.com/doc/refman/5.7/en/user-variables.html
SET #productTemp := '', #increment := 0;
SElECT
#increment := if(#productTemp != Product_id, #increment + 1, #increment) AS limiter,
#productTemp :=Product_id as Product_id,
Product.name,
Price.id as Price_id,
Price.start,
Price.end
FROM
Product
LEFT JOIN
Price ON Product.Id=Price.Product_id
WHERE
`start` >= '2013-05-01' AND `end` <= '2013-05-15'
GROUP BY
Price_id
HAVING
limiter <=2
What we're doing here is only incrementing the user defined var "incrementer" only when the product id is not the same as the last one that was encountered.
As aliases cannot be used in the WHERE condition, we must GROUP by the unique ID (in this case price ID) so that we can reduce the result using HAVING. In this case, I have a full result set that should include 3 Product IDs, reduced to only showing 2.
Please note: This is not a solution I would recommend on large data sets, or in a production enviornment. Even the mysql manual makes a point of highlighting that user defined vars can behave somewhat erratically depending on what paths the optimizer takes. However, I have used them to great effect for some internal statistics in the past.
Fiddle: http://sqlfiddle.com/#!2/96c92/3
It's hard to tell without sample data and desired output but you can try something like this
SElECT p.*, q2.*
FROM
(
SElECT Product_id
FROM Price
WHERE `start` >= '2013-05-01' AND `end` <= '2013-05-15'
GROUP BY Product_id
LIMIT 0,10
) q1 JOIN
(
SELECT *
FROM Price
WHERE `start` >= '2013-05-01' AND `end` <= '2013-05-15'
) q2 ON q1.Product_id = q2.Product_id JOIN product p
ON q1.Product_id = p.Id
Here is SQLFiddle demo

MySQL query to return number 'zero' if no results

When selecting a DATE and that date does not exist in my table it currently will return an empty result set. How can I be able to return the number zero for those empty result sets instead?:
SELECT SUM(TOTAL), SUM(5STAR), STORE, DATE
FROM `table` WHERE DATE >= '2012-02-24' GROUP BY TOTAL
MySQL returned an empty result set (i.e. zero rows)
I want to instead return the results of the SUM(TOTAL) and SUM(5STAR) (if zero rows) as the number zero (0).
FULL TABLE STRUCTURE:
ID = Primary
DATE = UNIQUE (date)
STORE
5STAR
4STAR
3STAR
2STAR
1STAR
TOTAL
FROM = UNIQUE
Try COALESCE
SELECT COALESCE(SUM(TOTAL),0), COALESCE(SUM(5STAR),0), STORE, DATE
FROM `table` WHERE DATE >= '2012-02-24' GROUP BY TOTAL
TRY
SELECT
IFNULL(SUM(TOTAL), 0) AS total,
IFNULL(SUM(5STAR), 0) AS FiveStar,
STORE,
DATE
FROM `table`
WHERE DATE >= '2012-02-24'
GROUP BY TOTAL
Reference
I think it would be easier to handle the empty result set on the PHP side (count the returned rows). If you want to handle it in the database, you should create a stored procedure.
DELIMITER $$
CREATE PROCEDURE `mc`.`new_routine` (IN DT DATETIME)
BEGIN
IF EXISTS (SELECT 1 FROM `table` WHERE DATE >= #DT)
THEN
SELECT SUM(TOTAL) AS SumTotal, SUM(5STAR) AS Sum5Star, STORE, `DATE`
FROM `table`
WHERE DATE >= #DT
GROUP BY TOTAL;
ELSE
SELECT 0 AS SumTotal, 0 AS Sum5Star, NULL AS STORE, NULL AS `DATE`;
END IF;
END