Avoid duplicate in query and make all the duplicated row blank - mysql

I have written query which should fetch ADJUSTMENT_AMOUNT and PAY_AMOUNT from the table ABC for different month, I have used subqueries to get this done, each ADJUSTMENT_AMOUNT and PAY_AMOUNT contains number, which is addition of multiple rows for the month by that each month I can see how much adjustment amount is available, same with PAY_AMOUNT.
But with the query that I have written, it is failing when ADJUSTMENT_AMOUNT is calculated using june,july,august,september month but PAY_AMOUNT is calculated using only september month, here in this condition PAY_AMOUNT is duplicated. I just want to avoid the duplicate values and make it blank, so basically only one row should be available for PAY_AMOUNT and rest 3 rows should be blank.
ADJ_MONTH ADJUSTMENT_AMOUNT CURRENCY PAY_MONTH PAY_AMOUNT
September 445 USD September 177.14
June 200 USD September 177.14
July 67 USD September 177.14
August 23 USD September 177.14
My query:
SELECT *
FROM
(SELECT TO_CHAR(CRE_DT, 'Month') AS ADJ_MONTH ,
SUM(ADJ_AMT) AS ADJUSTMENT_AMOUNT,
CURRENCY_CD
FROM ci_Adj
WHERE sa_id IN
(SELECT sa_id FROM ci_Sa WHERE acct_id=:F1
)
AND EXTRACT( YEAR FROM cre_dt) = EXTRACT(YEAR FROM sysdate)
GROUP BY TO_CHAR(CRE_DT, 'Month'),
CURRENCY_CD
ORDER BY TO_CHAR(CRE_DT, 'Month') DESC
),
(SELECT TO_CHAR(pae.cre_dttm, 'Month') AS PAY_MONTH ,
SUM(pa.PAY_AMT) AS PAY_AMOUNT
FROM ci_pay_event pae,
ci_pay pa
WHERE pa.acct_id =:F1
AND pa.pay_status_flg ='50'
AND pae.pay_event_id =pa.pay_event_id
AND EXTRACT( YEAR FROM pae.cre_dttm) = EXTRACT(YEAR FROM sysdate)
GROUP BY TO_CHAR(pae.cre_dttm, 'Month')
ORDER BY TO_CHAR(pae.cre_dttm, 'Month') DESC
)

Adding to points mentioned by (kfinity) and assuming the sub-queries are working fine. The below query will give all the data from the first sub-query and if it has any co-related data in the second query you get it, else the columns from query b will be null.
SELECT a.ADJ_MONTH, a.ADJUSTMENT_AMOUNT, a.CURRENCY_CD, b.PAY_MONTH, b.pay_amount
FROM
(SELECT TO_CHAR(CRE_DT, 'Month') AS ADJ_MONTH ,
SUM(ADJ_AMT) AS ADJUSTMENT_AMOUNT,
CURRENCY_CD
FROM ci_Adj
WHERE
sa_id IN (SELECT sa_id FROM ci_Sa WHERE acct_id=:F1)
AND EXTRACT( YEAR FROM cre_dt) = EXTRACT(YEAR FROM sysdate)
GROUP BY TO_CHAR(CRE_DT, 'Month'), CURRENCY_CD
ORDER BY TO_CHAR(CRE_DT, 'Month') DESC
)a,
(SELECT TO_CHAR(pae.cre_dttm, 'Month') AS PAY_MONTH ,
SUM(pa.PAY_AMT) AS PAY_AMOUNT
FROM ci_pay_event pae,
ci_pay pa
WHERE pa.acct_id =:F1
AND pa.pay_status_flg ='50'
AND pae.pay_event_id =pa.pay_event_id
AND EXTRACT( YEAR FROM pae.cre_dttm) = EXTRACT(YEAR FROM sysdate)
GROUP BY TO_CHAR(pae.cre_dttm, 'Month')
ORDER BY TO_CHAR(pae.cre_dttm, 'Month') DESC
)b
where a.ADJ_MONTH=b.pay_amount (+);

Related

SQL: SELECT AS multiple value with the same FROM with different WHERE

So i have this code:
SELECT a.total_sales AS July, b.total_sales AS August, c.total_sales AS September
FROM
(SELECT EXTRACT(month FROM delivered_at) AS month, ROUND(SUM (sale_price),2) AS total_sales
FROM `bigquery-public-data.thelook_ecommerce.order_items`
WHERE status = 'Complete' AND delivered_at BETWEEN "2022-01-01" AND "2022-10-01"
GROUP BY month
ORDER BY month) a,
(SELECT EXTRACT(month FROM delivered_at) AS month, ROUND(SUM (sale_price),2) AS total_sales
FROM `bigquery-public-data.thelook_ecommerce.order_items`
WHERE status = 'Complete' AND delivered_at BETWEEN "2022-01-01" AND "2022-10-01"
GROUP BY month
ORDER BY month) b,
(SELECT EXTRACT(month FROM delivered_at) AS month, ROUND(SUM (sale_price),2) AS total_sales
FROM `bigquery-public-data.thelook_ecommerce.order_items`
WHERE status = 'Complete' AND delivered_at BETWEEN "2022-01-01" AND "2022-10-01"
GROUP BY month
ORDER BY month) c
WHERE a.month = 7 AND b.month = 8 AND c.month = 9
I got the result that i wanted, which is this:
Row July August September
1 148622.29 169310.62 209339.57
Is there any simpler ways to do this?
We can reduce 3 subquerys into 1 subquery
SELECT
SUM(IF(t.month=7,t.total_sales,0)) AS July,
SUM(IF(t.month=8,t.total_sales,0)) AS August,
SUM(IF(t.month=9,t.total_sales,0)) AS September
FROM
(
SELECT EXTRACT(month FROM delivered_at) AS month, ROUND(SUM (sale_price),2) AS total_sales
FROM `bigquery-public-data.thelook_ecommerce.order_items`
WHERE status = 'Complete' AND delivered_at BETWEEN "2022-01-01" AND "2022-10-01"
AND month in(7,8,9)
GROUP BY month
) t

how to make cohort analysis in mysql

I have a table called order_star_member:
create table order_star_member(
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
users_id INT(11) NOT NULL,
createdAt datetime NOT NULL,
total_price_star_member decimal(10,2) NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO order_star_member(users_id, createdAt, total_price_star_member)
VALUES
(15, '2021-01-01', 350000),
(15, '2021-01-02', 400000),
(16, '2021-01-02', 700000),
(15, '2021-02-01', 350000),
(16, '2021-02-02', 700000),
(15, '2021-03-01', 350000),
(16, '2021-03-01', 850000),
(17, '2021-03-03', 350000);
DB Fiddle
I want to find users in the month March with transaction >= 700000 and first transaction >= 700000. The user whose transaction is >= 700000 is called star member.
My query so far:
SELECT COUNT(users_id) count_star_member,
year_and_month DIV 100 `year`,
year_and_month MOD 100 `month`
FROM (SELECT users_id,
MIN(year_and_month) year_and_month
FROM ( SELECT users_id,
DATE_FORMAT(createdAt, '%Y%m') year_and_month,
SUM(total_price_star_member) month_price
FROM order_star_member
GROUP BY users_id,
DATE_FORMAT(createdAt, '%Y%m')
HAVING month_price >= 350000 ) starrings
GROUP BY users_id
HAVING SUM(year_and_month = '202103') > 0 ) first_starrings
GROUP BY year_and_month
ORDER BY `year`, `month`;
+-------------------+------+-------+
| count_star_member | year | month |
+-------------------+------+-------+
| 1 | 2021 | 1 |
+-------------------+------+-------+
Explanation: in march 2021, there's only one 'star member', which is users_id 16, whose first transaction is in january 2021, so 'star member' in march 2021 is as above.
But starting from March, the definition of 'star member' changes from 700,000 to 350,000.
I want to find the 'star member' in March, and his first transaction, but if the first transaction is in a month before March 2021, then the star member should be the user whose transaction >= 700,000 -- but if the first transaction is in March 2021, as I sid, select a user whose transaction >= 350,000.
Thus my updated expectation:
+-------------------+------+-------+
| count_star_member | year | month |
+-------------------+------+-------+
| 2 | 2021 | 1 |
| 1 | 2021 | 3 |
+-------------------+------+-------+
Explanation : users 15, 16, and 17 are star member in march 2021. but users 15 and 16 are doing their first star member in January 2021 (because it is before March 2021, when the requirement to become star member is 700,000), while user 17 is also a star member because the first transaction is 350,000 in March 2021.
My understanding is that in determining the final output, you need 2 things:
A user's first transaction
The users who are star members for the requested month using the condition that before March 2021 cumulative monthly transaction amounts >=700000 and after March >=350000
If correct, since you are using a version less than 8.0(where it could be done with one statement) your solution is as follows:
You need a rules table or some configuration of rules (we'll call it SMLimitDef) which would look like this entered directly in a table:
insert into SMLimitDef(sEffDate,eEffDate,priceLimit)
VALUES('1980-01-01','2021-02-28',700000),
('2021-03-01','2999-12-31',350000);
Next, you need a query or view that figures out your first transactions(called vFirstUserTransMatch) which would look something like this:
create view vFirstUserTransMatch as
SELECT *,month(osm.createdAt) as createMonth, year(osm.createdAt) as createYear
FROM order_star_member osm
where createdAt=(select MIN(createdAt) from order_star_member b
where b.users_id=osm.users_id
)
Next you need a summary view or query that summarizes transactions per month per user
create view vOSMSummary as
SELECT users_id,month(osm.createdAt) as createMonth, year(osm.createdAt) as createYear, sum(total_price_star_member) as totalPrice
FROM order_star_member osm
group by users_id,month(osm.createdAt), year(osm.createdAt);
Next you need a query that puts it all together based on your criteria:
select osm.*,futm.createMonth as firstMonth, futm.createYear as firstYear
from vOSMSummary osm
inner join vFirstUserTransMatch futm
on osm.users_id=futm.users_id
where exists(select 'x' from SMLimitDef c
where osm.createMonth between Month(c.sEffDate) and Month(c.eEffDate)
and osm.createYear between Year(c.sEffDate) and Year(c.eEffDate)
and osm.totalPrice>=c.pricelimit
)
and osm.CreateMonth=3 and osm.createYear=2021
Lastly, you can do your summary
SELECT COUNT(users_id) count_star_member,
firstYear `year`,
firstMonth `month`
FROM (
select osm.*,futm.createMonth as firstMonth, futm.createYear as firstYear
from vOSMSummary osm
inner join vFirstUserTransMatch futm
on osm.users_id=futm.users_id
where exists(select 'x' from SMLimitDef c
where osm.createMonth between Month(c.sEffDate) and Month(c.eEffDate)
and osm.createYear between Year(c.sEffDate) and Year(c.eEffDate)
and osm.totalPrice>=c.pricelimit
)
and osm.CreateMonth=3 and osm.createYear=2021
) d
group by firstYear, firstMonth
Like I said, if you were using mySQL 8, everything could be in one query using "With" statements but for your version, for readability and simplicity, you need views otherwise you can still embed the sql for those views into the final sql.
Fiddle looks like this
Contrast with version 8 which looks like this
This is probably what you need:
SELECT min_year, min_month, COUNT(users_id)
FROM (
SELECT osm2.users_id, YEAR(min_createdAt) min_year, MONTH(min_createdAt) min_month, SUM(total_price_star_member) sum_price
FROM (
SELECT users_id, MIN(createdAt) min_createdAt
FROM order_star_member
GROUP BY users_id
) AS osm1
JOIN order_star_member osm2 ON osm1.users_id = osm2.users_id
WHERE DATE_FORMAT(osm2.createdAt, '%Y%m') = DATE_FORMAT(osm1.min_createdAt, '%Y%m')
GROUP BY osm2.users_id, min_createdAt
) t1
WHERE users_id IN (
SELECT users_id
FROM (
SELECT users_id, DATE_FORMAT(createdAt, '%Y-%m-01') month_createdAt
FROM order_star_member
WHERE DATE_FORMAT(createdAt, '%Y%m') = '202103'
GROUP BY users_id, DATE_FORMAT(createdAt, '%Y-%m-01')
HAVING SUM(total_price_star_member) >= (
CASE
WHEN date(month_createdAt) < date '2021-03-01' THEN 700000
ELSE 350000
END
)
) t3
) AND
(((min_year < 2021 OR min_month < 3) AND t1.sum_price >= 700000) OR
((min_year = 2021 AND min_month = 3) AND t1.sum_price >= 350000))
GROUP BY min_year, min_month
First you find the MIN(createdAt) for each member, with:
SELECT users_id, MIN(createdAt) min_createdAt
FROM order_star_member
GROUP BY users_id
Then you compute the SUM of all the total_price_star_member in the month of the min_createdAt date:
SELECT osm2.users_id, YEAR(min_createdAt) min_year, MONTH(min_createdAt) min_month, SUM(total_price_star_member) sum_price
FROM osm1
JOIN order_star_member osm2 ON osm1.users_id = osm2.users_id
WHERE DATE_FORMAT(osm2.createdAt, '%Y%m') = DATE_FORMAT(osm1.min_createdAt, '%Y%m')
GROUP BY osm2.users_id, min_createdAt
Next you filter on the month you are interested in. Here you cannot use HAVING with something that cannot be computed from what you have in the GROUP BY statement, so you need to project also DATE_FORMAT(createdAt, '%Y-%m-01') to establish the minimum total price for star membership in the HAVING clause that is now allowed.
SELECT users_id
FROM (
SELECT users_id, DATE_FORMAT(createdAt, '%Y-%m-01') month_createdAt
FROM order_star_member
WHERE DATE_FORMAT(createdAt, '%Y%m') = '202102'
GROUP BY users_id, DATE_FORMAT(createdAt, '%Y-%m-01')
HAVING SUM(total_price_star_member) >= (
CASE
WHEN date(month_createdAt) < date '2021-03-01' THEN 700000
ELSE 350000
END
)
) t3
In the end you check also for the min_month and min_year, then you group based on these attributes and COUNT how many members in each group.
SELECT min_year, min_month, COUNT(users_id)
FROM t1
WHERE users_id IN (...) AND
(((min_year < 2021 OR min_month < 3) AND t1.sum_price >= 700000) OR
((min_year = 2021 AND min_month = 3) AND t1.sum_price >= 350000))
GROUP BY min_year, min_month
I have not immediately understood what your goal is and I am not sure I get it now, that is why I changed this query a few times by now so you might be able to simplify it.

MySQL adding a year over year comparison

I'm new to SQL and trying to calculate YoY Sales over different stores with quarterly granularity. Table is as follows
So far I have:
SELECT Store_number, SUM(Sales) AS Sales_q1_2018
FROM table1
WHERE Sale_date BETWEEN '2018-01-01' AND '2018-03-31'
GROUP BY Store_number
ORDER BY Sales_Q1_2018
I need to do add a column with the following calculation: (sum(sales q1 2018) - sum(sales q1 2017)) / sum(sales q1 2017)
How can I set different date parameters for a temporary calculation? Thanks
You can use a subquery for retrive the 2017 q1 join these to you actual query
SELECT Store_number, SUM(Sales) AS Sales_q1_2018, (SUM(Sales) - Sales_q1_2107)/Sales_q1_2107
FROM table1
INNER JOIN (
SELECT Store_number, SUM(Sales) AS Sales_q1_2107
FROM table1
WHERE Sale_date BETWEEN '2017-01-01' AND '2017-03-31'
GROUP BY Store_number
) t2 t2.Store_number = table1.Store_number
WHERE Sale_date BETWEEN '2018-01-01' AND '2018-03-31'
GROUP BY Store_number
Try this:
SELECT Store_number,
((SUM(IF(year(Sale_date)='2018',Sales,0))
-SUM(IF(year(Sale_date)='2017',Sales,0)))
/SUM(IF(year(Sale_date)='2017',Sales,0))) Q1_2018_vs_2017
FROM table1
WHERE QUARTER(Sale_date)=1 AND YEAR(Sale_date) IN ('2017','2018')
GROUP BY Store_number;
DEMO ON SQL FIDDLE
Assuming your quarters are calendar quarters, I would write the query as:
SELECT Store_number,
SUM(CASE WHEN YEAR(Sale_date) = 2017 THEN Sales ELSE 0 END) AS Sales_q1_2018,
SUM(CASE WHEN YEAR(Sale_date) = 2018 THEN Sales ELSE 0 END) AS Sales_q1_2017,
(SUM(CASE WHEN YEAR(Sale_date) = 2018 THEN Sales ELSE - Sales END) /
SUM(CASE WHEN YEAR(Sale_date) = 2017 THEN Sales END)
) as calculation
FROM table1
WHERE YEAR(Sale_date) IN (2017, 2018) AND
MONTH(Sale_date) IN (1, 2, 3)
GROUP BY Store_number
ORDER BY Sales_Q1_2018;
Here is the SQL Fiddle.
This is similar to #cdaiga's answer, but with the following important differences:
Functions such as YEAR() return numbers, so the comparisons are to numbers, not strings.
CASE expressions are the ANSI-standard way of including conditional logic in a query. IF() is MySQL-specific.
The ratio protects against division by 0.

Query that shows the total profit in descending order for each month in 2010?

I am supposed to write a query that shows the total profit in descending order for each month in 2010.
So far, I have a query that shows the total profits in descending order for 2010 and I have a query that can extract each month, but I can't seem to connect the two.
select (SalesPrice - AcquisitionPrice) profit, datesold
from Transaction
where DateSold >= '10-jan-01' and DateSold <= '10-dec-31';
and:
select to_char(datesold, 'mon')
from transaction
group by to_char(datesold, 'mon')
Oracle Query:
SELECT TRUNC( datesold, 'MM' ) AS "Month",
SUM( SalesPrice - AcquisitionPrice ) AS profit
FROM Transaction
WHERE EXTRACT( YEAR FROM datesold ) = 2010
GROUP BY TRUNC( datesold, 'MM' )
ORDER BY TRUNC( datesold, 'MM' ) DESC
MySQL and Oracle Query:
SELECT EXTRACT( MONTH FROM datesold ) AS mnth,
SUM( SalesPrice - AcquisitionPrice ) AS profit
FROM Transaction
WHERE EXTRACT( YEAR FROM datesold ) = 2010
GROUP BY EXTRACT( MONTH FROM datesold )
ORDER BY EXTRACT( MONTH FROM datesold ) DESC

getting multi row data into columns in mysql

SELECT
Day,
month,
year,
GROUP_CONCAT(total),
GROUP_CONCAT(SP_ID)
FROM
(
SELECT
DATE_FORMAT(l.act_date, '%d') AS DAY,
DATE_FORMAT(l.act_date, '%M') AS MONTH,
EXTRACT(YEAR FROM l.act_date) AS YEAR,
COUNT(*) as total,l.sp_id
FROM lead_activity2 as l
right outer join salesperson as s on l.sp_id=s.sp_id
WHERE l.act_name='scb'
AND ((l.act_date>='2012-09-07 13:03:27' )
AND (l.act_date<= '2012-11-07 13:03:27'))
GROUP BY MONTH, YEAR, DAY, l.sp_id
ORDER BY YEAR DESC, MONTH DESC, DAY DESC, l.sp_id DESC
) t GROUP BY day, month, year
http://sqlfiddle.com/#!2/1514d/3 - you can view the scheme and the query,
what i would like to get is
18 | october | 2012 | 0,0,1,1 | 6,5,4,3
spid 6 and spid 5 have no data for 18 october but still should be shown tried doing right join and right outer join both dont seem to work...
Use GROUP_CONCAT like so:
SELECT
Day,
month,
year,
GROUP_CONCAT(total),
GROUP_CONCAT(SP_ID)
FROM
(
SELECT
DATE_FORMAT(l.act_date, '%d') AS DAY,
DATE_FORMAT(l.act_date, '%M') AS MONTH,
EXTRACT(YEAR FROM l.act_date) AS YEAR,
COUNT(*) as total,l.sp_id
FROM lead_activity2 as l
WHERE l.act_name='scb'
AND ((l.act_date>='2012-09-07 13:03:27' )
AND (l.act_date<= '2012-11-07 13:03:27'))
GROUP BY MONTH, YEAR, DAY, l.sp_id
ORDER BY YEAR DESC, MONTH DESC, DAY DESC, l.sp_id DESC
) t GROUP BY day, month, year
Updated SQL Fiddle
Update: Yes you can do this, but use LEFT JOIN to include non matching sp_id. These non matching ids will have a value of NULL use IFNULL to display it with zeros like so:
SELECT
Day,
month,
year,
GROUP_CONCAT(total) Total,
GROUP_CONCAT(SP_ID) 'List of sp_ids'
FROM
(
SELECT
DATE_FORMAT(l.act_date, '%d') AS DAY,
DATE_FORMAT(l.act_date, '%M') AS MONTH,
EXTRACT(YEAR FROM l.act_date) AS YEAR,
COUNT(*) as total,
IFNULL(s.sp_id , 0) sp_id
FROM lead_activity2 as l
LEFT JOIN salesperson s ON l.sp_id = s.sp_id
WHERE l.act_name='scb'
AND ((l.act_date>='2012-09-07 13:03:27' )
AND (l.act_date<= '2012-11-07 13:03:27'))
GROUP BY MONTH, YEAR,DAY,s.sp_id
) t
ORDER BY YEAR DESC,
MONTH DESC,
DAY DESC,
sp_id DESC
Updates SQL Fiddle Demo