How to create query that groups Years - mysql

I have this query:
SELECT Count(tblpeople.PersonID) AS Total,
YEAR(CURRENT_TIMESTAMP) - YEAR(tblmembership.MemberSince) AS YearsMember
FROM tblmembership
INNER JOIN tblpeople ON tblmembership.PersonID = tblpeople.PersonID
WHERE
tblpeople.MemSTATUS = 'Back from VPM'
OR tblpeople.MemSTATUS = 'Sent To VPM 2'
OR tblpeople.MemSTATUS = 'Sent To VPM 1'
OR tblpeople.MemSTATUS = 'Back from VPM'
OR tblpeople.MemSTATUS = 'Renewal'
OR tblpeople.MemSTATUS = 'Active Member'
GROUP BY YearsMember
HAVING YearsMember
ORDER BY YearsMember
It gets counts on years people have been a member.
What has me stumped is the boss wants it broken down by:
0 - 2,
3 -5,
6 - 10,
11 - 20,
>20
So counts for each. I can not figure out how to do this, though I'm sure there's a way!
I can just bring this into excel and manually group the counts, but wanted it done all in one shot.
Thanks

Use a case when expression: http://docs.oracle.com/cd/B19306_01/server.102/b14200/expressions004.htm
case when years between 0 and 2 then ... else ...

Use IN instead of all those OR statements. Then use a CASE statement to group your results:
SELECT Count(tblpeople.PersonID) AS Total,
CASE
WHEN YEAR(CURRENT_TIMESTAMP) - YEAR(tblmembership.MemberSince) BETWEEN 0 AND 2
THEN '0-2'
WHEN YEAR(CURRENT_TIMESTAMP) - YEAR(tblmembership.MemberSince) BETWEEN 3 AND 5
THEN '3-5',
...
WHEN YEAR(CURRENT_TIMESTAMP) - YEAR(tblmembership.MemberSince) > 20
THEN '>20'
END AS YearsMember
FROM tblmembership
INNER JOIN tblpeople ON tblmembership.PersonID = tblpeople.PersonID
WHERE tblpeople.MemSTATUS IN ( 'Back from VPM', 'Sent To VPM 2', 'Sent To VPM 1',
'Back from VPM', 'Renewal', 'Active Member')
GROUP BY YearsMember
ORDER BY YearsMember
SQL Fiddle Demo

Related

SQL: Add multiple where clauses in a single query

I want to run an SQL query from Node.js. I am currently showing the total number of projects that have a specific status in each of the 4 quarters. What I want to do now is show the same result but while adding one more condition i.e, Fiscal Year.
Here's my code for the 4 quarters:
SELECT
SUM(CurrentStatus = 'On Hold') onHold_Q,
SUM(CurrentStatus = 'In Progress') inProgress_Q,
SUM(CurrentStatus = 'Not Started') notStarted_Q,
SUM(CurrentStatus = 'Completed') completed_Q,
SUM(CurrentStatus = 'Routine Activity') routineActivity_Q,
SUM(CurrentStatus = 'Done But Not Published') doneButNotPublished_Q
FROM office.officedata
WHERE Quarter in ('Q1', 'Q2', 'Q3', 'Q4') //here I want to add one more condition FiscalYear in ('2016-17')
GROUP BY Quarter
ORDER BY Quarter;
This prints output in 4 different rows which is exactly what I want. But, when I add one more condition then it does execute but only one row gets printed since the projects in the rest of the 3 quarters for that year do not exist. I want to somehow print 4 different rows for quarters for every Fiscal Year. The query should print 0 in the row if no projects exist for that quarter in a year. How can I do that? My SQL is not that strong, it would be great if someone can help me out.
Can you tell me programmatically how to do that?
SELECT years.FiscalYear, quarters.Quarter,
SUM(CurrentStatus = 'On Hold') onHold_Q,
SUM(CurrentStatus = 'In Progress') inProgress_Q,
SUM(CurrentStatus = 'Not Started') notStarted_Q,
SUM(CurrentStatus = 'Completed') completed_Q,
SUM(CurrentStatus = 'Routine Activity') routineActivity_Q,
SUM(CurrentStatus = 'Done But Not Published') doneButNotPublished_Q
FROM (SELECT 2016 FiscalYear UNION SELECT 2017) years
CROSS JOIN (SELECT 'Q1' Quarter UNION SELECT 'Q2' UNION SELECT 'Q3' UNION SELECT 'Q4') quarters
LEFT JOIN office.officedata ON office.FiscalYear = years.FiscalYear AND office.Quarter = quarters.Quarter
GROUP BY years.FiscalYear, quarters.Quarter
ORDER BY years.FiscalYear, quarters.Quarter;
Maybe SUMs must be wrapped with COALESCE() for to replace NULLs to zeros.

SQL - how to display products' options, whether or not they have them?

Assume a products can have 0 or more options -- implemented with the following tables:
products
- id
- name
options
- id
- name
product_options
- id
- product_id
- option_id
Further assume the following products have the following options:
Product 1 = Option 1, Option 2, Option 3
Product 2 = Option 2, Option 3, Option 4
Product 3 = Option 3, Option 4, Option 5
How can I query this so that I get results like this:
Product 1, Option 1, Option 2, Option 3, NULL, NULL
Product 2, NULL, Option 2, Option 3, Option 4, NULL
Product 3, NULL, NULL, Option 3, Option 4, Option 5
My options are actually a nested tree. And they have a foreign key to a categories table (also a nested tree). Ultimately, I need to be able to do this query and group the results by category. However, I probably should understand first how to solve this simpler version of my problem.
UPDATE 1: I do not know in advance what the options might be. Also, there is no limit to the number of options a product may have.
If you have an unknown number of options you could use a stored procedure to dynamically create a query that can be used to pivot your table. Something like this:
CREATE PROCEDURE display_options()
BEGIN
SET #query = 'SELECT p.id, ';
SET #query = CONCAT(#query, (SELECT GROUP_CONCAT(CONCAT('MAX(CASE WHEN o.name = ''', name, ''' THEN o.name END) AS `', name, '`')) FROM options ORDER BY id));
SET #query = CONCAT_WS(' ', #query,
'FROM products p',
'JOIN product_options po ON po.product_id = p.id',
'JOIN options o ON o.id = po.option_id',
'GROUP BY p.id');
PREPARE stmt FROM #query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
This procedure will produce a query like this (essentially the same as in #GordonLinoff's answer for the sample data in your question):
SELECT p.name,
MAX(CASE WHEN o.name = 'Option 1' THEN o.name END) AS `Option 1`,
MAX(CASE WHEN o.name = 'Option 2' THEN o.name END) AS `Option 2`,
MAX(CASE WHEN o.name = 'Option 3' THEN o.name END) AS `Option 3`,
MAX(CASE WHEN o.name = 'Option 4' THEN o.name END) AS `Option 4`,
MAX(CASE WHEN o.name = 'Option 5' THEN o.name END) AS `Option 5`
FROM products p
JOIN product_options po ON po.product_id = p.id
JOIN options o ON o.id = po.option_id
GROUP BY p.name
which can then be prepared and executed to give results like this:
name Option 1 Option 2 Option 3 Option 4 Option 5
Product 1 Option 1 Option 2 Option 3
Product 2 Option 2 Option 3 Option 4
Product 3 Option 3 Option 4 Option 5
demo on dbfiddle
After so many of trials I found below query that will return exact result as you want even if options and product may increases.
SELECT CONCAT(P.productName, ",", GROUP_CONCAT(COALESCE(IF(P.optionId IN (product_options.option_id),P.optionName,NULL),"NULL") order by P.optionId)) AS result
FROM product_options
RIGHT JOIN (SELECT products.id as productId, products.name as productName,options.id as optionId, options.name AS optionName from products,options)
as P On P.productId = product_options.product_id AND P.optionId = product_options.option_id
GROUP BY P.productId
You can use conditional aggregation, if I understand correctly:
select po.product_id,
max(case when o.name = 'Option 1' then o.name end) as option_1,
max(case when o.name = 'Option 2' then o.name end) as option_2,
max(case when o.name = 'Option 3' then o.name end) as option_3,
max(case when o.name = 'Option 4' then o.name end) as option_4,
max(case when o.name = 'Option 5' then o.name end) as option_5
from product_options po join
options o
on po.option_id = o.id
group by po.product_id
Do you really need to structure the display at the DB level?
Honestly, if it's at all feasible, I'd save myself the headace and run:
SELECT p.*, o.*
FROM product_options po
JOIN products p
ON p.id = po.product_id
JOIN options o
ON o.id = po.option_id
WHERE ...
ORDER BY ...
You can then sort out your desired structure at the application level as you iterate through the result set
Product options are important in ecommerce, usually more complicated than above given question.
For example in any standard application say 'Opencart' we have following 6 tables for options
Table occw_option - columns: option_id, type
Table occw_option_description -columns: option_id, language_id, name
Table occw_option_value -columns: option_id, option_value_id, image
Table occw_option_value_description - columns: option_value_id, language_id,option_id, name
Table occw_product_option -colums: product_option_id, product_id, option_id, value
Table occw_product_option_value - columns: product_option_value_id,product_option_id, product_id, option_id, option_value_id
quantity, price price_prefix, points, points_prefix, weight, weight_prefix
Easiest and best thing to do is to use left joins
like for example say your 'product_id' is '59'
SELECT product_option_value_id,product_option_id, product_id, quantity, price, price_prefix, points, points_prefix, weight, weight_prefix, occw_option_description.name, occw_option_description.language_id, occw_option.type,occw_option_value.image, occw_option_value_description.name
FROM `occw_product_option_value`
left join `occw_option` on occw_product_option_value.option_id = occw_option.option_id
left join `occw_option_description` on occw_product_option_value.option_id = occw_option_description.option_id
left join `occw_option_value` on occw_product_option_value.option_value_id = occw_option_value.option_value_id
left join `occw_option_value_description` on occw_option_value.option_value_id = occw_option_value_description.option_value_id
WHERE occw_product_option_value.product_id = 59
You will get nicely all info you require for your front end like below
product_option_value_id, product_option_id, product_id, quantity,price, price_prefix, points, points_prefix, weight, weight_prefix, name, language_id, type, image, name
84 250 59 111 0.00 + 0 + 0.00 + Brand 1 radio XLL
83 250 59 122 0.00 + 0 + 0.00 + Brand 1 radio XL
82 250 59 45 0.00 + 0 + 0.00 + Brand 1 radio M
81 250 59 34 0.00 + 0 + 0.00 + Brand 1 radio L
These above are say for 'product' shirt sizes options XLL, XL, M, L which a customer can choose by 'radio' at the front end

MYSQL - SUM values of last 10 days

I have this table in mysql:
| player1 | player2 | date | fs_1 | fs_2 |
Jack Tom 2015-03-02 10 2
Mark Riddley 2015-05-02 3 1
...
I need to know how many aces (fs_1) player 1 have done BEFORE the match reported in date_g (10 days before for example).
This is what i tried without success:
OPTION 1
SELECT
players_atp.name_p AS 'PLAYER 1',
P.name_p AS 'PLAYER 2',
DATE(date_g) AS 'DATE',
result_g AS 'RESULT',
FS_1,
FS_2,
SUM(IF(date_sub(date_g, interval 10 day)< date_g, FS_1, 0)) AS 'last 10 days'
FROM
stat_atp stat_atp
JOIN
backup3.players_atp ON ID1 = id_P
JOIN
backup3.players_atp P ON P.id_p = id2
JOIN
backup3.games_atp ON id1_g = id1 AND id2_g = id2
AND id_t_g = id_t
AND id_r_g = id_r
WHERE
date_g > '2015-01-01'
GROUP BY ID1;
OPTION 2
SELECT
players_atp.name_p AS 'PLAYER 1',
P.name_p AS 'PLAYER 2',
DATE(date_g) AS 'DATE',
result_g AS 'RESULT',
FS_1,
FS_2,
SUM(CASE WHEN date_g between date_g and date_sub(date_g, interval 10 day) then fs_1 else 0 end) AS 'last 10 days'
FROM
stat_atp stat_atp
JOIN
backup3.players_atp ON ID1 = id_P
JOIN
backup3.players_atp P ON P.id_p = id2
JOIN
backup3.games_atp ON id1_g = id1 AND id2_g = id2
AND id_t_g = id_t
AND id_r_g = id_r
WHERE
date_g > '2015-01-01'
GROUP BY ID1;
I have edited the code, now is more easy to read and understand.
SELECT
id1 AS 'PLAYER 1',
id2 AS 'PLAYER 2',
DATE(date_g) AS 'DATE',
result_g AS 'RESULT',
FS_1,
FS_2,
SUM(CASE
WHEN date_g BETWEEN date_g AND DATE_SUB(date_g, INTERVAL 10 DAY) THEN fs_1
END) AS 'last 20 days' FROM
stat_atp stat_atp
JOIN
backup3.games_atp ON id1_g = id1 AND id2_g = id2
AND id_t_g = id_t
AND id_r_g = id_r GROUP BY ID1;
Thanx in advance.
Maybe this could help you:
SELECT
id1,
SUM(fs_1)
FROM
stat_atp
WHERE
date_g <= DATE_SUB('2015-03-02', INTERVAL 1 DAY) AND date_g >= DATE_SUB('2015-03-02', INTERVAL 10 DAY)
AND
id1='Jack'
GROUP BY id1;
Remember that RDBMS are used to construct rigorous data sets that are linked between each others by clear ids (keys when talking about SQL). It's easier to respect the three first normal forms. That's why you should use keys to identify your match itself. By this way you could use subqueries (subsets) to achieve your goal.
Then, keep in mind that SQL is STRUCTURED. It's its force and weakness cause you won't be able to use it as a Turing complete programming langage with loops and conditions. But in any situation you will be able to find the same structure for a query. However you can interact with a SQL query result with another langage and use loops and condition on the result set itself. That's up to you.
Anyway, you may want to read about the MySQL GROUP BY clause which is different from the ISO SQL form : https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html

How to query last 2 business days only

I'm running a query that pulls the correct information I'm looking for, but I need it to pull the last 2 business days rather than the last 2 days. This comes into play when it's Monday and my results show information for Monday and Sunday rather than Monday and Friday. How can I change my query to pull in business days only?
USE [LetterGeneration]
SELECT g.LetterGenerationPrintJobId
,CAST(t.[TemplateKey] AS VarChar) AS LetterCode
,convert(char(12),r.CreatedDate,101) AS CreatedDate
,s.LetterGenerationStatusId AS Status
,s.StatusKey AS StatusDesc
,count(g.LetterGenerationId) as LetterCount
,c.BankingDateYorN
FROM [LetterGenerationTemplateRequest] AS r
INNER JOIN [LetterGenerationTemplate] AS t
ON t.[LetterGenerationTemplateId] = r.LetterGenerationTemplateId
INNER JOIN LetterGeneration g
ON g.LetterGenerationTemplateRequestId = r.LetterGenerationTemplateRequestId
INNER JOIN LetterGenerationStatus s
ON g.LetterGenerationStatusId = s.LetterGenerationStatusId
INNER JOIN Enterprise..Calendar C
ON c.BeginDate = g.LetterDate
WHERE ((DATEDIFF(d, r.CreatedDate, GETDATE()) = 0) OR (DATEDIFF(d, r.CreatedDate, GETDATE()) = 1))
--BankingDateYorN = 1
--AND RelativeTimeValue_BusinessDates =-1
AND t.[TemplateKey] NOT LIKE '%PLTV1%'
AND s.LetterGenerationStatusId NOT LIKE '4'
AND s.LetterGenerationStatusId NOT LIKE '16'
AND s.LetterGenerationStatusId NOT LIKE '19'
AND s.LetterGenerationStatusId NOT LIKE '20'
AND s.LetterGenerationStatusId NOT LIKE '38'
GROUP BY r.[LetterGenerationTemplateRequestId]
,r.LetterGenerationTemplateId
,g.Lettergenerationprintjobid
,t.[TemplateKey]
,r.[Loan_no]
,r.CreatedDate
,r.[CreatedBy]
,s.LetterGenerationStatusId
,s.StatusKey
,c.BankingDateYorN
ORDER BY r.CreatedDate DESC
UPDATE: I've recently discovered how to join a calendar table to my current query. The calendar query has a column called BusinessDayYorN with 1's for a business day and 0's for weekends and holidays. I've also updated the old query to now include the join.
select *
from LetterGenerationTemplateRequest
where createddate >= (
getdate() -
case datename(dw,getdate())
when 'Tuesday' then 5
when 'Monday' then 4
else 3
end
)
--and datename(dw,createdDate) not in ('Saturday','Sunday',datename(dw,getdate()))
and datename(dw,createdDate) not in ('Saturday','Sunday')
;
Assuming that you always want to include the last two non-weekend days you can try this:
; with aux as (
select diff = case
when datename(weekday, getdate()) in ('Tuesday', 'Wednesday ', 'Thursday', 'Friday') then 1
else
case datename(weekday, getdate())
when 'Saturday' then 2
when 'Sunday' then 3
when 'Monday' then 4
end
end
)
SELECT --r.[LetterGenerationTemplateRequestId]
--,r.LetterGenerationTemplateId
g.LetterGenerationPrintJobId
,CAST(t.[TemplateKey] AS VarChar) AS LetterCode
,r.[Loan_no]
,convert(char(12),r.CreatedDate,101) AS CreatedDate
-- ,g.ModifiedDate
-- ,convert(varchar(18), g.ModifiedDate - r.CreatedDate, 108) AS TimeSpan
,s.LetterGenerationStatusId AS Status
,s.StatusKey AS StatusDesc
,count(g.LetterGenerationId) as LetterCount
FROM [LetterGenerationTemplateRequest] AS r
INNER JOIN [LetterGenerationTemplate] AS t
ON t.[LetterGenerationTemplateId] = r.LetterGenerationTemplateId
INNER JOIN LetterGeneration g
ON g.LetterGenerationTemplateRequestId = r.LetterGenerationTemplateRequestId
INNER JOIN LetterGenerationStatus s
ON g.LetterGenerationStatusId = s.LetterGenerationStatusId
WHERE
DATEDIFF(day, r.CreatedDate, GETDATE()) <= (select diff from aux)
AND t.[TemplateKey] NOT LIKE '%PLTV1%'
AND s.LetterGenerationStatusId NOT LIKE '4'
AND s.LetterGenerationStatusId NOT LIKE '16'
AND s.LetterGenerationStatusId NOT LIKE '19'
AND s.LetterGenerationStatusId NOT LIKE '20'
AND s.LetterGenerationStatusId NOT LIKE '38'
GROUP BY r.[LetterGenerationTemplateRequestId]
,r.LetterGenerationTemplateId
,g.Lettergenerationprintjobid
,t.[TemplateKey]
,r.[Loan_no]
,r.CreatedDate
-- ,g.ModifiedDate
,r.[CreatedBy]
,s.LetterGenerationStatusId
,s.StatusKey
ORDER BY r.CreatedDate DESC
The CTE aux returns a dataset with only one record and only one field, the value of which is the number of days you need to go back in your WHERE statement.

MYSQL different conditions in a single query

Hello
I have following columns in mysql table: rating1, rating2, price, cond, approved
Is it possible to select results like this:
select
average rating1 + rating2 as total_rating,
average rating1 as rating1,
average rating2 as rating2,
average price if cond = '1' as price_used
average price if cond = '2' as price_new
where approved = '1'
So far I have:
SELECT
(AVG(t.rating1) + AVG(t.rating2)) / 2 AS total_rating
AVG(t.rating1) AS rating1,
AVG(t.rating2) AS rating2,
---- price statements?? ----
FROM t
WHERE 1=1
AND t.approved = '1'
Many thanks and excuse me for my English
Try this:
SELECT (AVG(t.rating1) + AVG(t.rating2)) / 2 AS total_rating,
AVG(t.rating1) AS rating1,
AVG(t.rating2) AS rating2,
AVG(IF(cond='1', price, NULL)) price_used,
AVG(IF(cond='2', price, NULL)) price_new
FROM t
WHERE 1=1
AND t.approved = '1'
EDIT: Updated the query to get desired result.
Standard SQL, works across "all" major dbms:
select (avg(t.rating1) + avg(t.rating2)) / 2 as total_rating
,avg(t.rating1) as rating1
,avg(t.rating2) as rating2
,avg(case when cond = '1' then price end) as price_used
,avg(case when cond = '2' then price end) as price_new
from t
where t.approved = '1'
I don't think doing an average on the If statement will work. But here is the syntax for the IF.
IF(condition, value_to_display_if_true, value_to_display_if_false)
So, for example, IF(1=1, 'true', 'false') would always display 'true' for this column because 1 does = 1.