How to select sql with if else use column - mysql

I'm beginner for mysql and this my graduation project please help me.
//AMOUNT_PEOPLE is variable in nodejs
//place_id is variable in nodejs recieve from front end.
SELECT
(IF AMOUNT_PEOPLE >= 10
RETURN COLUMN package_tb.price_group*(AMOUNT_PEOPLE-1)
ELSE IF AMOUNT_PEOPLE >= 6
RETURN COLUMN package_tb.price_group*(AMOUNT_PEOPLE-1) - (SELECT option_tb.price_group FROM option_tb WHERE obtion_tb.place_id = place_id)
ElSE
RETURN COLUMN price_normal*AMOUNT_PEOPLE
END IF) AS price,name,detail
FROM package_tb WHERE package_tb.place_id = place_id
This ticket booking program
Logic is
Check number of tourist
if tourist >= 10 must use group_price and free 1 person include food for free person option
but if tourist >= 6 must use group_price
and free 1 person but not include food for free person option
finally tourist 0-5 must use normal_price
Such customer tell me "I want ticket for 10 tourist" the system will check as above explain.
package_tb
-package_id
-place_id
-name
-detail
-price_group
-price_normal
option_tb
-option_id
-place_id
-name
-price_group
-price_normal
place_tb
-place_id
-name
If tourist use price group option have to use price group only
But tourist use price normal option have to use price normal only.
Sorry for my bad english.

There exists a CASE function.
Which is a standard SQL function for switch logic.
Based on the updated question I'm assuming that there can be multiple options per place.
So with node.js variables:
SET #place_id = ${PLACE_ID};
SET #amount_people = ${AMOUNT_PEOPLE};
SELECT
CASE
WHEN #amount_people >= 10
THEN (p.price_group * (#amount_people - 1))
WHEN #amount_people >= 6
THEN (p.price_group * (#amount_people - 1)) - SUM(o.price_group)
ELSE p.price_normal
END AS price,
p.name,
p.detail
FROM package_tb p
LEFT JOIN option_tb o ON o.place_id = p.place_id
WHERE p.place_id = #place_id
GROUP BY p.package_id, p.place_id, p.price_normal, p.price_group, p.name, p.detail;
A test on rextester here

-- From which table you want use price_normal? tb or o ???
SELECT
CASE WHEN p.AMOUNT_PEOPLE >= 4
THEN p.price_group
WHEN p.AMOUNT_PEOPLE >= 3
THEN p.price_group - o.price_food
ELSE tb.price_normal
END AS price,
p.name, p.detail
FROM package_tb p JOIN option_tb o ON o.package_id = p.package_id;

There are two main control-flow functions in MySQL IF() and CASE
Since you are not comparing the value of AMOUNT_PEOPLE directly, CASE is a bit of overkill, which can be simplified slightly by using IF.
The syntax for IF is IF(<expr>, <true_result>, <false_result>). This allows you to perform else if by chaining another IF() as the false_result
IF(<expr>, ..., IF(<expr>, ..., ...))
Instead of using else if, you only need to remove the option_tb.price_group when AMOUNT_PEOPLE is fewer than 10 to get your desired pricing.
/* groups with 6 or more use group price and one person free */
IF(AMOUNT_PEOPLE >= 6,
/* groups with fewer than 10 people remove option */
p.price_group*(AMOUNT_PEOPLE-1) - IF(AMOUNT_PEOPLE < 10,
o.price_group,
0
),
p.price_normal*AMOUNT_PEOPLE
) AS price
This reduces the amount of code slightly, to determine when to subtract a person.
Instead of using a nested sub-query, which would be executed for each row returned. If the option_tb.place_id is unique, a JOIN would be more preferable.
If option_tb.place_id is not unique, you would need to use a GROUP BY. One approach is to JOIN using a sub-query, to avoid false matching on the join table groupings.
To ensure results are not excluded when a row in the option_tb table fails to match a place_id, you would use a LEFT JOIN that returns NULL instead of excluding the row.
Then you can use COALESE(<column>, 0) to retrieve the value from the column or 0 if the column value is NULL.
In NodeJS you can use ${var} to inject a variable into a string.
For example:
var place_id = 1;
var query = 'SELECT ${place_id};';
console.log(query);
Results in
SELECT 1;
Putting it all together.
Example: db-fiddle
SELECT
IF(${AMOUNT_PEOPLE} >= 6,
p.price_group*(${AMOUNT_PEOPLE}-1) - IF(${AMOUNT_PEOPLE} < 10, COALESCE(o.price_group, 0), 0),
p.price_normal*${AMOUNT_PEOPLE}
) AS price,
p.name,
p.detail
FROM package_tb AS p
LEFT JOIN (
SELECT
place_id,
SUM(price_group) AS price_group
FROM option_tb
GROUP BY place_id
) AS o
ON o.place_id = p.place_id
WHERE p.place_id = ${place_id};

Related

How select count distinct (unique truckers) without group by function and maybe without using Having (not sure about last)

I have a task, but couldn't solve it:
There are truckers and they have to travel between cities.
We have data of these travels in our database in 2 tables:
trucker_traffic
tt_id (key)
date
starting_point_coordinate
destination_coordinate
traveller_id
event_type ('travel', 'accident')
parent_event_id (For 'accident' event type it's tt_id of the original travel. There might be few accidents within one travel.)
trucker_places
coordinate (key)
country
city
I need SQL query to pull the number of all unique truckers who travelled more than once from or to London city in June 2020.
In the same query pull the number of these travels who got into an accident.
Example of my tries
SELECT
count(distinct(tt.traveller_id)),
FROM trucker_traffic tt
JOIN trucker_places tp
ON tt.starting_point_coordinate = tp.coordinate
OR tt.destination_coordinate = tp.coordinate
WHERE
tp.city = 'London'
AND month(tt.date) = 6
AND year(tt.date) = 2020
GROUP BY tt.traveller_id
HAVING count(tt.tt_id) > 1
But it's select count distinct truckers with grouping and works only if I had one tracker in db
For second part of task (where I have select number of travels with accident - I think that good to use function like this
SUM(if(count(tt_id = parent_event_id),1,0))
But I'm not sure
This is rather complicated, so make sure you do this step by step. WITH clauses help with this.
Steps
Find travels from and to London in June 2020. You can use IN or EXISTS in order to see whether a travel had accidents.
Group the London travels by traveller, count travels and accident travels and only keep those travellers with more than one travel.
Take this result set to count the travellers and sum up their travels.
Query
with london_travels as
(
select
traveller_id,
case when tt_id in
(select parent_event_id from trucker_traffic where event_type = 'accident')
then 1 else 0 end as accident
from trucker_traffic tt
where event_type = 'travel'
and month(tt.date) = 6
and year(tt.date) = 2020
and exists
(
select
from trucker_places tp
where tp.coordinate in (tt.starting_point_coordinate, tt.destination_coordinate)
and tp.city = 'London'
)
)
, london_travellers as
(
select
traveller_id,
count(*) as travels,
sum(accident) as accident_travels
from london_travels
group by traveller_id
having count(*) > 1;
)
select
count(*) as total_travellers,
sum(travels) as total_travels,
sum(accident_travels) as total_accident_travels
from london_travellers;
If your MySQL version doesn't support WITH clauses, you can of course just nest the queries. I.e.
with a as (...), b as (... from a) select * from b;
becomes
select * from (... from (...) a) b;
You say in the request title that you don't want GROUP BY in the query. This is possible, but makes the query more complicated. If you want to do this I leave this as a task for you. Hint: You can select travellers and count in subqueries per traveller.

msAccess query with Sum and extra criteria

I need to get all of the Costs values for a Dog in a specific month. When I use this code with Access it says the join operation is not supported. Is there a better way to accomplish this in MS Access? I need all of the dog names to come back even if they don't have a cost associated with them for a specific month
Select Dog.DogName, Dog.DogOwner, Sum(Costs.CostAmount)
From
(Dog Left join Costs on Dog.DogName = Costs.DogName and Costs.CostMonth = 10)
Group by Dog.DogName, Dog.OwnerName
Try this:
Select
Dog.DogName, Dog.DogOwner, Sum(Costs.CostAmount) As TotalAmount
From
Dog
Left join
Costs
On
(Dog.DogName = Costs.DogName)
Where
Costs.CostMonth <= Month(Date())
Or
Costs.CostMonth Is Null
Group by
Dog.DogName, Dog.OwnerName
SELECT Dogs.DogName
, Dogs.OwnerName
, (
SELECT SUM(Costs.CostAmountAmount)
FROM Costs
WHERE Dogs.DogName = Costs.DogName AND
Costs.CostMonth =NumMonth
)
FROM Dogs;

Selecting rows until a column value isn't the same

SELECT product.productID
, product.Name
, product.date
, product.status
FROM product
INNER JOIN shelf ON product.sheldID=shelf.shelfID
WHERE product.weekID = $ID
AND product.date < '$day'
OR (product.date = '$day' AND shelf.expire <= '$time' )
ORDER BY concat(product.date,shelf.expire)
I am trying to stop the SQL statement at a specific value e.g. bad.
I have tried using max-date, but am finding it hard as am making the time stamp in the query. (Combining date/time)
This example table shows that 3 results should be returned and if the status "bad" was the first result than no results should be returned. (They are ordered by date and time).
ProductID Date status
1 2017-03-27 Good
2 2017-03-27 Good
3 2017-03-26 Good
4 2017-03-25 Bad
5 2017-03-25 Good
Think I may have fixed it, I added this to my while loop.
The query gives the results in order by present to past using date and time, this while loop checks if the column of that row is equal to 'bad' if it is does something (might be able to use an array to fill it up with data). If not than the loop is broken.
I know it doesn't seem ideal but it works lol
while ($row = mysqli_fetch_assoc($result)) {
if ($row['status'] == "bad") {
$counter += 1;
}
else{
break;}
I will provide an answer just with your output as if it was just one table. It will give you the main ideia in how to solve your problem.
Basically I created a column called ord that will work as a row_number (MySql doesn't support it yet AFAIK). Then I got the minimum ord value for a bad status then I get everything from the data where ord is less than that.
select y.*
from (select ProductID, dt, status, #rw:=#rw+1 ord
from product, (select #rw:=0) a
order by dt desc) y
where y.ord < (select min(ord) ord
from (select ProductID, status, #rin:=#rin+1 ord
from product, (select #rin:=0) a
order by dt desc) x
where status = 'Bad');
Result will be:
ProductID dt status ord
-------------------------------------
1 2017-03-27 Good 1
2 2017-03-27 Good 2
3 2017-03-26 Good 3
Also tested with the use case where the Bad status is the first result, no results will be returned.
See it working here: http://sqlfiddle.com/#!9/28dda/1

MySQl count grouping by 4 columns

Basically, this query returns me different values from counts()
Geographic Address(city),Office,Device type, Device unique type identifier, number case by device type
0001,1002,ORDENADOR,ORD1234,5 INCIDENCIAS
0001,1002,ORDENADOR,ORD3333,2 INCIDENCIAS
0001,1002,ORDENADOR,ORD2222,1 INCIDENCIAS
0001,1002,TECLADO,TECYYYY,2 INCIDENCIAS
0001,1002,TECLADO,TECXXXX,4 INCIDENCIAS
0001,1002,PANTALLA,PAN0000,1 INCIDENCIAS
Select
d.dt as 'Direccion Territorial',
t.centro as 'Oficina',
nombrelargo,
if(length(p.Oficina)=3,concat('0',p.Oficina),p.Oficina) as 'Oficina2',
p.Tipo_Disp as 'Dispositivo',
count(p.Tipo_Disp) as 'Nº de partes/Etiqueta',
p.Etq_Amarilla as 'Etiqueta',
------------ count(TOTAL INC DE ESE DISPOSITIVO) ---------------------------,
------------ count(TOTAL INC DE ESA OFICINA) ---------------------------
from textcentro t,dtdz d,ppp p
where
t.jcentro03=d.dt and
t.organizativo='OFIC./AGEN./DELEG.' and
t.situacion='ABIERTO' and
t.sociedad='0900' and
(p.Estado != "Abierto" and p.Estado!= 'Planificado') and
(month(p.Fecha_y_hora_de_creacion) = 8 and year(Fecha_y_hora_de_creacion)=2013) and
t.centro=if(length(p.Oficina)=3,concat('0',p.Oficina),p.Oficina)
GROUP BY d.dt,t.centro,p.Tipo_Disp,p.Etq_Amarilla
The grouping:
1 - d.dt ----> Postal code
2 - t.centro ----> Office code
3 - p.Tipo_Disp ----> Device Type
4 - d.Etq_Amarilla ----> Unique identifier for this device
The tables are :
1- textcentro ----> Specific information of the offices
2- dtdz ----> auxiliary table to find the Postal Code of the office
3- ppp ----> Table where we can find all the cases
So now, I want to sum the total number of cases by device type, should be this:
Postal Code,Office,Device type, Unique identifier for Device, total number of cases by unique identifier device, total number case by device type, total number case by office
0001,1002,ORDENADOR,ORD1234,5 INCIDENCIAS,8 INC,15
0001,1002,ORDENADOR,ORD3333,2 INCIDENCIAS,8 INC,15
0001,1002,ORDENADOR,ORD2222,1 INCIDENCIAS,8 INC,15
0001,1002,TECLADO,TECYYYY,2 INCIDENCIAS,6 INC,15
0001,1002,TECLADO,TECXXXX,4 INCIDENCIAS,6 INC,15
0001,1002,PANTALLA,PAN0000,1 INCIDENCIAS,1 INC,15
I'm trying with sums and counts functions but i dont reach it, i don't have any way to take the last two columns. I think that i can try to take this number by sub-query in the column but the performance will be down too much.
The example would be this... but even i get to finish the query and im waiting around 12-13 minutes.
Select
d.dt as 'Direccion Territorial',
t.centro as 'Oficina',
nombrelargo,
if(length(p.Oficina)=3,concat('0',p.Oficina),p.Oficina) as 'Oficina2',
p.Tipo_Disp as 'Dispositivo',
count(p.Tipo_Disp) as 'Nº de partes/Etiqueta',
p.Etq_Amarilla as 'Etiqueta',
(Select count(*) from People_DB pp where pp.Oficina=p.Oficina and pp.Tipo_Disp=Dispositivo and (month(pp.Fecha_y_hora_de_creacion) = 8 and year(pp.Fecha_y_hora_de_creacion)=2013) and (pp.Estado != "Abierto" and pp.Estado!= 'Planificado') )
from textcentro t,dtdz d,ppp p
where
t.jcentro03=d.dt and
t.organizativo='OFIC./AGEN./DELEG.' and
t.situacion='ABIERTO' and
t.sociedad='0900' and
(p.Estado != "Abierto" and p.Estado!= 'Planificado') and
(month(p.Fecha_y_hora_de_creacion) = 8 and year(Fecha_y_hora_de_creacion)=2013) and
t.centro=if(length(p.Oficina)=3,concat('0',p.Oficina),p.Oficina)
GROUP BY d.dt,t.centro,p.Tipo_Disp,p.Etq_Amarilla
Sorry for my poor english, maybe this post is unintelligible
May I make some suggestions:
First, your choice of tables looks like this:
from textcentro t,dtdz d,ppp p
For the sake of clarity I suggest you employ explicit JOIN statements instead. For example
FROM textcentro AS t
JOIN dtdx AS d ON t.jcentro03=d.dt
JOIN ppp AS p ON XXXXXXXXX
You may want to use LEFT JOIN in cases for example, where there might be no corresponding row in dtdx to go with a row in textcentro.
I cannot tell from your sample query what the ON constraint for the JOIN to ppp should be. I have shown that with XXXXXXXXX in my code above. I think your condition is this:
t.centro=if(length(p.Oficina)=3,concat('0',p.Oficina),p.Oficina)
but that is a nasty expression to compute, and therefore very slow. It looks like your t.centro is a char column containing an integer with leading zeros, and your p.Oficina is the same but without the leading zeros. Instead of adding the leading zero to p.Oficina, try stripping it from the t.centro column.
CAST(t.centro AS INTEGER) = p.Oficina
Keep in mind that without a simple JOIN constraint you get a combinatorial explosion: m times n rows. This makes things slow and possibly wrong.
So, your table selection becomes:
FROM textcentro AS t
JOIN dtdx AS d ON t.jcentro03=d.dt
JOIN ppp AS p ON CAST(t.centro AS INTEGER) = p.Oficina
Second, your date/time search expressions are not built for speed. Try this:
p.Fecha_y_hora_de_creacion >= '2013-08-01'
AND p.Fecha_y_hora_de_creacion < '2013-08-01' + INTERVAL 1 MONTH
If you have an index on your p.Fecha... column, this will permit a range-scan search on that column.
Third, this item in your SELECT list is killing performance.
(Select count(*)
from People_DB pp
where pp.Oficina=p.Oficina
and pp.Tipo_Disp=Dispositivo
and (month(pp.Fecha_y_hora_de_creacion) = 8
and year(pp.Fecha_y_hora_de_creacion)=2013)
and (pp.Estado != "Abierto" and pp.Estado!= 'Planificado') )
Refactor this to be a virtual table in your JOIN list, as follows.
(SELECT COUNT(*) AS NumPersonas,
Oficina,
Tipo_Disp
FROM People_DB
WHERE Fecha_y_hora_de_creacion >= '2013-08-01'
AND Fecha_y_hora_de_creacion < '2013-08-01' + INTERVAL 1 MONTH
AND Estado != 'Abierto'
AND Estado != 'Planificado
GROUP BY Oficina, Tipo_Disp
) AS pp_summary ON ( pp_summary.Oficina=p.Oficina
AND pp_summary.Tipo_Disp=Dispositivo)
So, this is your final list of tables.
FROM textcentro AS t
JOIN dtdx AS d ON t.jcentro03=d.dt
JOIN ppp AS p ON CAST(t.centro AS INTEGER) = p.Oficina
JOIN (
SELECT COUNT(*) AS NumPersonas,
Oficina,
Tipo_Disp
FROM People_DB
WHERE Fecha_y_hora_de_creacion >= '2013-08-01'
AND Fecha_y_hora_de_creacion < '2013-08-01' + INTERVAL 1 MONTH
AND Estado != 'Abierto'
AND Estado != 'Planificado
GROUP BY Oficina, Tipo_Disp
) AS pp_summary ON ( pp_summary.Oficina=p.Oficina
AND pp_summary.Tipo_Disp=Dispositivo)
Three of these tables are "physical" tables, and the fourth is a "virtual" table, constructed as a summary of the physical table called People_DB.
You can include
pp_summary.NumPersonas
in your SELECT list.
Fourth, avoid the nonstandard extensions to MySQL GROUP BY functionality, and use standard SQL. Read this for more information.
http://dev.mysql.com/doc/refman/5.0/en/group-by-extensions.html
Fifth, add appropriate indexes to your tables.

Combine 2 SELECTs into one SELECT in my RAILS application

I have a table called ORDEREXECUTIONS that stores all orders that have been executed. It's a multi currency application hence the table has two columns CURRENCY1_ID and CURRENCY2_ID.
To get a list of all orders for a specific currency pair (e.g. EUR/USD) I need to lines to get the totals:
v = Orderexecution.where("is_master=1 and currency1_id=? and currency2_id=? and created_at>=?",c1,c2,Time.now()-24.hours).sum("quantity").to_d
v+= Orderexecution.where("is_master=1 and currency1_id=? and currency2_id=? and created_at>=?",c2,c1,Time.now()-24.hours).sum("unitprice*quantity").to_d
Note that my SUM() formula is different depending on the the sequence of the currencies.
e.g. If I want the total ordered quantities of the currency pair USD it then executes (assuming currency ID for USD is 1 and EUR is 2.
v = Orderexecution.where("is_master=1 and currency1_id=? and currency2_id=? and created_at>=?",1,2,Time.now()-24.hours).sum("quantity").to_d
v+= Orderexecution.where("is_master=1 and currency1_id=? and currency2_id=? and created_at>=?",2,1,Time.now()-24.hours).sum("unitprice*quantity").to_d
How do I write this in RoR so that it triggers only one single SQL statement to MySQL?
I guess this would do:
v = Orderexecution.where("is_master=1
and ( (currency1_id, currency2_id) = (?,?)
or (currency1_id, currency2_id) = (?,?)
)
and created_at>=?"
,c1, c2, c2, c1, Time.now()-24.hours
)
.sum("CASE WHEN currency1_id=?
THEN quantity
ELSE unitprice*quantity
END"
,c1
)
.to_d
So you could do
SELECT SUM(IF(currency1_id = 1 and currency2_id = 2, quantity,0)) as quantity,
SUM(IF(currency2_id = 1 and currency1_id = 2, unitprice * quantity,0)) as unitprice _quantity from order_expressions
WHERE created_at > ? and (currency1_id = 1 or currency1_id = 2)
If you plug that into find_by_sql you should get one object back, with 2 attributes, quantity and unitprice_quantity (they won't show up in the output of inspect in the console but they should be there if you inspect the attributes hash or call the accessor methods directly)
But depending on your indexes that might actually be slower because it might not be able to use indexes as efficiently. The seemly redundant condition on currency1_id means that this would be able to use an index on [currency1_id, created_at]. Do benchmark before and after - sometimes 2 fast queries are better than one slow one!