How to use group by and case when - mysql

There is six games and I want to write query to output the net sales amount for each games and total sales based on Top 10 Retailers. Also, I will be great if you can output the percent of total sales for each retailer. Here is MySQL query:
SELECT Concat(css.retailerid,'-',rmd.retailer) AS retailer,
rmd.city,
CASE
WHEN css.gameid= 1 THEN Sum(css.netsales)
END AS 'Meqa 5/36',
CASE
WHEN css.gameid= 2 THEN Sum(css.netsales)
END AS '6/40',
CASE
WHEN css.gameid= 3 THEN Sum(css.netsales)
END AS '4+4',
CASE
WHEN css.gameid= 6 THEN Sum(css.netsales)
END AS 'Meqa 5',
CASE
WHEN css.gameid= 9 THEN Sum(css.netsales)
END AS 'Super Keno',
Sum(css.netsales) AS netsalesamount,
-- sum(css.NetSales)*100/(select sum(ss.NetSales) from reporting.core_sales_statistics AS ss) AS PERCENT,
-- sum(css.netsalesqty) AS netsalesqty,
-- Sum(css.NetSalesQty)*100/(select sum(ss.NetSalesQty) from reporting.core_sales_statistics AS ss) AS PERCENT
FROM reporting.core_sales_statistics AS css
LEFT JOIN reporting.retailer_master_data AS rmd ON css.retailerid=rmd.retailerid
WHERE css.datastatus=1
AND rmd.reportinglevel IN ('Exclusive Agents', 'Distributors' )
AND rmd.reportinglevel3 IN ('Exclusive Agents', 'Distributor Agents')
AND css.sourcesystem ='AEGIS'
AND css.transactiondate = curdate() -1
AND css.gamecategoryid=4
GROUP BY css.retailerid,
rmd.retailer
ORDER BY netsalesamount DESC limit 10
;

Related

MySQL CASE statement returning null

I have two 'identical' operations
(1)
SELECT ticket_type_name
, amount as 'original_amount'
, CASE ticket_type_name
WHEN ticket_type_name = 'General Admission - Friday'
and amount > 0 THEN amount / 400
END AS 'NewAmount'
from analytics.deleteme
(2)
select distinct ticket_type_name, amount, amount/400 AS NewAmount
from analytics.deleteme
where amount > 0
and ticket_type_name = 'General Admission - Friday'
The first statement generates a value of NULL
The second statement generates a correct value of 1 (400/400)
I have added an ELSE statement at the end:
SELECT DISTINCT ticket_type_name
, amount as 'original_amount'
, CASE ticket_type_name
WHEN ticket_type_name = 'General Admission - Friday' and amount > 0 THEN amount / 400
else amount/575
END AS 'NewAmount'
from analytics.deleteme
where amount > 0
Results are the reverse of what they should be!
GA Friday should be 400/400 = 1
Everything else, eg GA Weekend should be divided by 575 (=0.82, not 1.18!)
ticket_type_name, amount, NewAmount
General Admission - Friday 400.0 0.69
General Admission - Weekend 475.0 1.18
General Admission - Saturday 375.0 0.93
Children (12 to 14 Years) - Weekend 100.0 0.25
Children (12 to 14 Years) - Saturday 50.0 0.125
I could replicate your bug.
Just remove ticket_type_name after CASE and it works:
SELECT
DISTINCT ticket_type_name,
amount AS 'original_amount',
CASE
WHEN
ticket_type_name = 'General Admission - Friday'
AND amount > 0
THEN
amount / 400
ELSE
amount / 575
END AS 'NewAmount'
FROM analytics.deleteme
WHERE amount > 0;
You don't need to put anything between CASE and WHEN but it's funny how this broke the query. The first comment below perfectly explains why.
As an alternative, you may move ticket_type_name after CASE and put the comparison value after WHEN. Like this:
SELECT
DISTINCT ticket_type_name,
amount AS 'original_amount',
CASE ticket_type_name
WHEN
'General Admission - Friday'
THEN
amount / 400
ELSE
amount / 575
END AS 'NewAmount'
FROM analytics.deleteme
WHERE amount > 0;
Long story short: either use CASE field WHEN value… or CASE WHEN condition…. You don't want to use CASE field WHEN condition….

Calculate sum when value changes

I am trying to build a system that will track vehicle fuelings, and have run into a problem with one report; determining fuel efficiency in distance/fuel. Sample data is:
odometer
fuel
partial_fillup
61290
10.3370
0
61542
6.4300
0
61735
4.3600
0
61994
7.5000
0
62242
5.4070
0
62452
8.1100
0
62713
5.7410
1
62876
9.4850
0
63243
6.1370
1
63499
10.7660
0
Where odometer is the total distance the vehicle has traveled, fuel is the number of gallons or liters put in, and partial_fillup is a boolean meaning the fuel tank was not completely filled if non-zero.
If the user fills the tank each time the query I can use is:
set #a = null;
select
odometer,
odometer-previousOdometer distance,
fuel,
(odometer-previousOdometer)/fuel mpg,
partial_fillup
from
(
select
#a as previousOdometer,
#a:=odometer,
odometer,
fuel/1000 fuel,
partial_fillup
from fuel
where
vehicle_id =1
and odometer >= 61290
order by odometer
) as readings
where readings.previousOdometer is not null;
However, when the user only partially fills the tank, the correct procedure would be to subtract the last full fueling from current odometer reading, then divide by the sum of all fuel since the previous odometer reading, so at odometer 63499, the calculate would be (63499-62876)/(10.7660+6.1370)
This will get the average used on the last ride:
select
odometer,
odometer-lag(odometer) over (order by odometer) as distance,
fuel,
(odometer-lag(odometer) over (order by odometer))/fuel as mpg
from fuel
output:
odometer
distance
fuel
mpg
61290
10.3370
61542
252
6.4300
39.1913
61735
193
4.3600
44.2661
61994
259
7.5000
34.5333
62242
248
5.4070
45.8665
62452
210
8.1100
25.8940
62713
261
5.7410
45.4625
62876
163
9.4850
17.1850
63243
367
6.1370
59.8012
63499
256
10.7660
23.7786
Or you can calculate the total drive distance, and the total amount of fuel used:
select
distance,
sum_fuel,
distance/sum_fuel as mpg
from (
select
f.odometer,
f.odometer-(select min(odometer) from fuel) as distance,
fuel,
sum_fuel
from fuel f
inner join (
select
odometer,
sum(fuel) over (order by R) as sum_fuel
from (
select
odometer,
fuel,
row_number() over (order by odometer) R
from fuel) x
) x on x.odometer = f.odometer
) x2
which will get next output, which will get closer to an average after a longer time of measurement:
distance
sum_fuel
mpg
0
10.3370
0.0000
252
16.7670
15.0295
445
21.1270
21.0631
704
28.6270
24.5922
952
34.0340
27.9720
1162
42.1440
27.5721
1423
47.8850
29.7170
1586
57.3700
27.6451
1953
63.5070
30.7525
2209
74.2730
29.7416
DBFIDDLE
I was able to figure it out after studying Luuk's answer. I'm sure there is a more efficient way to do this; I am not used to using variables in SQL. But, the answers are correct in the test data.
set #oldOdometer = null;
set #totalFuel = 0;
select
s.odometer,
format(fuel, 3) fuel,
s.distance,
format( distance / fuel, 2) as mpg
from (
select
partial_fillup as partial,
odometer,
(fuel+#totalFuel) as fuel,
#totalFuel as totalFuel,
#oldOdometer oldOdometer,
if ( partial_fillup, null,odometer - #oldOdometer ) as distance,
#totalFuel := if ( partial_fillup, #totalFuel + fuel, 0) as pastFuel,
#oldOdometer := if (partial_fillup,#oldOdometer,odometer ) as runningOdometer
from
fuel
order by
odometer ) s
where s.distance is not null
order by s.odometer
limit 1,999;
limit 1,999 simply there to skip the first row returned, since there is not enough data to calculate distance or mpg. On my copy of MySQL, doing this means you do not need to initialize the two variables (you don't have to include the set commands at the beginning), so it works with my reporting tool very well. If you do initialize them, you do not need the limit statement. Works assuming you don't have more than 999 rows returned.

Order a column in mysql by if it contains a letter

I have a column named convoys in my worker-database and I first want to sort the 'non-letter-containing'-values and after them the 'letter-containing' ones.
For example here are a few values
name
convoy
worker1
1
worker2
3
worker3
M 4
worker4
M5
worker5
4
worker6
11
and it should sort them like this
name
convoy
worker1
1
worker2
3
worker5
4
worker6
11
worker3
M 4
worker4
M5
Has anybody some kind of idea how to make this query working?
I think the most direct way to do that would be the following (using regular expression):
SELECT name,
convoy
FROM TABLE_NAME
ORDER BY CASE WHEN convoy REGEXP '^[0-9]+$' THEN convoy ELSE convoy END
Order by the first token combined from characters that are not digits or spaces, and then by the first token combined from digits (and converted to UNSIGNED).
Please note that this solution also order correctly strings such as 'M13' (which comes after 'M 4' and 'M5')
select *
from t
order by regexp_substr(convoy, '[^\\d\\s]+')
,cast(regexp_substr(convoy, '\\d+') as UNSIGNED)
fiddle
A maximal unsigned integer: 4294967295
MySQL 5.6
This SQL sorts 'non-letter-containing' values as numbers, then others.
select
name,
convoy
from Table1
order by
case when
convoy REGEXP '^[0-9]+$'
then convert(convoy, UNSIGNED INTEGER)
else 4294967295
end,
convoy
;
Or maybe better:
select
*
from Table1
order by
case when
convoy REGEXP '^[0-9]+$'
then LPAD(convert(convoy, UNSIGNED INTEGER),10,0)
else convoy
end
;
DDL:
CREATE TABLE Table1
(`name` varchar(7), `convoy` varchar(3))
;
INSERT INTO Table1
(`name`, `convoy`)
VALUES
('worker1', '1'),
('worker2', '3'),
('worker3', 'M 4'),
('worker4', 'M5'),
('worker5', '4'),
('worker6', '11')
;
Output:
name
convoy
worker1
1
worker2
3
worker5
4
worker6
11
worker3
M 4
worker4
M5

Checking to see if today is in the scheduled day utilizing a binary mapping table in SQL

I am pulling XML data from a 3rd party application using SQL Server 2008 (which I only have read-only access to the DB) and it stores the day or days of the week a job is supposed to run in one of those XML fields.
SQL uses a recursive day code of Sunday=1, Monday=2, Tuesday=4, Wednesday=8, Thursday=16, Friday=32, Saturday=64.
I am pulling the day from this XML field like the following.
case
when (job.SJDefn.value('(schedules/schedule/name)[1]', 'varchar(30)') ) like '%Week%'
then job.SJDefn.value('(/schedules/schedule/recurring/date/week/day_of_week)[1]', 'int')
else 0
end as JDOW,
I was originally utilizing the this to determine the current date of the week as but the values were obviously not compatible.
DATEPART(dw, getdate()) AS CDOW, -- Sun 1 Mon 2 Tue 3 Wed 4 Thu 5 Fri 6 Sat 7`
So I moved to:
case DATEPART(dw, getdate())
when 1 then 1 -- Sunday (1=1)
when 2 then 2 -- Monday (2=2)
when 3 then 4 -- Tueday (3=4
when 4 then 8 -- Wednesday (4=8)
when 5 then 16 -- Thursday (5=16)
when 6 then 32 -- Friday (6=32)
when 7 then 64 -- Saturday (7=64)
else NULL
end as CDOW,
The challenge is that it is an easy translation when the job just runs one day of a week.. but what about Monday/Wednesday/Friday well that is 42 which is an aggregate of the days Monday (2) + Wednesdays (8) + Friday (32).
I could translate this to text like in this question and do a string compare to a temp table but that seems inefficient.
I know that there is a table that can be built like this code to build a comparison table and I've checked the SQL Server Agent documentation (which this isn't but it is quite similar.
It seems like all possible combinations for Sunday - Saturday are basically a bitmap ranged from 1-127.. such as 42 = 0010 1010 which could be an on/off values for each day (first position always 0, 127 = 0111 111) and with that.
Position 1 = Always 0 ; binary 0000 0000
Position 2 = Sunday 1 ; binary 0000 0001
Position 3 = Monday 2 ; binary 0000 0010
Position 4 = Tueday 4 ; binary 0000 0100
Position 5 = Wednesday 8 ; binary 0000 1000
Position 6 = Thursday 16 ; binary 0001 0000
Position 7 = Friday 32 ; binary 0010 0000
Position 8 = Saturday 64 ; binary 0100 0000
I am thinking about how to potentially use a bitwise & operator but it compares the entire bit for an exact match and not a single position as I understand it so not thinking it will accomplish exactly what I want.
What I want is if the current day is in the schedule I get a true / false result from the comparison. I don’t care about interpreting the values into plain English.. For example if the string was 0011 1110 (Monday - Friday), then if the current day value is the equivalent of 0000 0010 (Monday) I be get true. If it was Sunday (0000 0001) it would be false if the reference was that 0010 1110.
I would really think there is a much simpler way of simply checking if the current day falls into the configuration in a few lines vs. building a temporary table to compare against.
So my question: given the information above, is there a simple function / query that I can execute to compare the two and return a boolean result (0/1 or Y/N) if the the current day matches the schedule?
This will give you a 1/0 result for the current date given a schedule bitmap:
declare #Today as Date = GetDate();
-- Assuming that ##DateFirst is correctly set:
declare #DoW as Int = DatePart( weekday, #Today );
-- Shift a bit to the appropriate position.
declare #Mask as Int = Power( 2, #DoW - 1 );
-- Sample schedule bitmap.
declare #Schedule as Int = 42;
-- Sign is used to convert the result of the bitwise-and to a 0 or 1.
-- Any positive value, indicating the corresponding bit is set, will return 1.
-- If there is no match, the result will be zero.
select #Today as Today, #DoW as Dow, #Mask as Mask, #Schedule as Schedule,
Sign( #Schedule & #Mask ) as IsScheduledToday;
As a professor once said, "you're always off by one in this business." It may need a tweak, but should be close.
It's a little difficult to tell exactly what you're trying to do here. You've explained the problem, but didn't provide any desired output. Since you have read-only access, however, I assume you're only interested in querying the data.
However, if you have an integer like 42, and you want to test bitwise if the bit for Monday is set (that is, 2's place), then you do this:
42 & 2 = 2
If you want to display days of the week you could do something like:
SELECT
CASE WHEN JDOW & 1 = 1 THEN 'U' ELSE '' END
+ CASE WHEN JDOW & 2 = 2 THEN 'M' ELSE '' END
+ CASE WHEN JDOW & 4 = 4 THEN 'T' ELSE '' END
+ CASE WHEN JDOW & 8 = 8 THEN 'W' ELSE '' END
+ CASE WHEN JDOW & 16 = 16 THEN 'R' ELSE '' END
+ CASE WHEN JDOW & 32 = 32 THEN 'F' ELSE '' END
+ CASE WHEN JDOW & 64 = 64 THEN 'S' ELSE '' END AS scheduled_days
FROM (VALUES (42),(84),(96), (4)) UnnamedTable (JDOW)
If you want you could create another table:
CREATE TABLE BitwiseWeekDay (
code tinyint primary key not null,
day_name nvarchar(10) not null,
day_short_name nvarchar(4) not null,
day_code nvarchar(1) not null
)
INSERT INTO BitwiseWeekDay VALUES
(1,'Sunday','Sun','U'),
(2,'Monday','Mon','M'),
(4,'Tuesday','Tue','T'),
(8,'Wednesday','Wed','W'),
(16,'Thurday','Thur','R'),
(32,'Friday','Fri','F'),
(64,'Saturday','Sat','U')
SELECT u.JDOW,
b.code,
b.day_name
FROM (VALUES (42),(84),(96), (4)) u (JDOW)
INNER JOIN BitwiseWeekDay b
ON u.JDOW & b.code = b.code
ORDER BY u.JDOW, b.code
But, I don't really know what you're looking for.

mysql,select query ,and or clause problem

i have a table named item with four attribute name,code,class,value
now i want to group them in following way:
group a: name='A',code=11,class='high',value between( (5300 and 5310),(7100 and 7200),(8210 and 8290))
group b: name='b',code=11,class='high',value between( (1300 and 1310),(2100 and 2200),(3210 and 3290))
how can i do it?
You might want to try something like this:
SELECT
CASE
WHEN code = 11 AND
class = 'high' AND
(code BETWEEN 5300 AND 5310 OR
code BETWEEN 7100 AND 7200 OR
code BETWEEN 8210 AND 8290)
THEN 'A'
WHEN code = 11 AND
class = 'high' AND
(code BETWEEN 1300 AND 1310 OR
code BETWEEN 2100 AND 2200 OR
code BETWEEN 3210 AND 3290)
THEN 'B'
ELSE Unknown
END AS name,
*
FROM your_table
ORDER BY name
You might wish to change ORDER BY to GROUP BY and you should be aware that BETWEEN includes both endpoints.
First group
select * from item
where name LIKE 'A'
and code LIKE '11'
and class LIKE 'high'
and (value BETWEEN 5300 AND 5310 OR value BETWEEN 7100 AND 7200 OR value BETWEEN 8210 AND 8290)
the same idea for group b