Unified a data based on dates - mysql

I Have this query
SELECT DATE_FORMAT(bookings.start_at, '%m/%d/%Y') as "Date",
CASE WHEN payment.type = "CASH" THEN sum(payment.amount) end as "Cash" ,
CASE WHEN payment.type = "Credit" THEN sum(payment.amount) end as "Credit"
from orders
inner join bookings on bookings.id = orders.booking_id
inner join payment on payment.order_id = orders.id
where (bookings.start_at BETWEEN '2022-03-09' AND '2022-03-09 23:59:00')
group by payment.type;
And it returning like this
Date | Cash | Credit
2022/03/09 | NULL | NULL
2022/03/09 | 2000 | NULL
2022/03/09 | NULL | 5000
What i want to achieve is like this
Date | Cash | Credit
2022/03/09 | 2000 | 5000
i already tried GROUP BY for start_at but it return a different kind of time but same date

SELECT DATE_FORMAT(bookings.start_at, '%m/%d/%Y') as `Date`,
SUM(CASE WHEN payment.type = "CASH"
THEN payment.amount
ELSE 0
END) as `Cash`,
SUM(CASE WHEN payment.type = "Credit"
THEN payment.amount
ELSE 0
END) as `Credit`
from orders
inner join bookings on bookings.id = orders.booking_id
inner join payment on payment.order_id = orders.id
where bookings.start_at BETWEEN '2022-03-09' AND '2022-03-09 23:59:00'
group by 1;

Related

CASE statement when criterias are in different rows and columns

DB Fiddle
CREATE TABLE sales (
campaign VARCHAR(255),
event_type VARCHAR(255),
event_date VARCHAR(255),
quantity VARCHAR(255)
);
INSERT INTO sales
(campaign, event_type, event_date, quantity)
VALUES
("C001", "buy", "2019-05-08", "500"),
("C001", "sale", "2019-04-20", "400"),
("C002", "buy", "2019-06-07", "800"),
("C002", "sale", "2019-06-15", "900"),
("C003", "buy", "2020-02-23", "700"),
("C003", "sale", "2020-03-17", "300"),
("C004", "buy", "2020-04-05", "200"),
("C004", "sale", "2020-03-17", "600");
The table displays the event_types (buy, sell) and the corresponing quantities per campaign.
Now, I want to run a query which checks the following:
a) event_date for event_type buy > event_date for event_type sell
b) quantity for event_type buy > quantity for event_type sell
The result should look like this:
campaign Check_Date Check_Quantity
C001 Error OK
C002 OK Error
C004 OK Error
I think I need to go with the CASE statement somehow like this:
SELECT
campaign,
(CASE WHEN event_date of buy > event_date of sale THEN "Error" ELSE "OK") AS Check_Date,
(CASE WHEN quantity of buy > quantity of sale THEN "Error" ELSE "OK") AS Check_Quantity
quantity
FROM sales;
You want to compare values across rows, so this suggests aggregation. You can then implement the logic with case expressions:
select
campaign,
case when
max(case when event_type = 'buy' then event_date end)
> max(case when event_type = 'sale' then event_date end)
then 'OK'
else 'Error'
end check_date,
case when
max(case when event_type = 'buy' then quantity end)
> max(case when event_type = 'sale' then quantity end)
then 'OK'
else 'Error'
end check_date
from sales
group by campaign;
In your DB Fiddle, this produces:
campaign | check_date | check_date
:------- | :--------- | :---------
C001 | OK | OK
C002 | Error | Error
C003 | Error | OK
C004 | OK | Error
Split the table in 2 queries, then join and use CASE expressions:
select
b.campaign,
case when b.event_date > s.event_date then 'Error' else 'OK' end Check_Date,
case when b.quantity > s.quantity then 'Error' else 'OK' end Check_Quantity
from (select * from sales where event_type = 'buy') b
inner join (select * from sales where event_type = 'sale') s
on s.campaign = b.campaign
See the demo.
Results:
> campaign | Check_Date | Check_Quantity
> :------- | :--------- | :-------------
> C001 | Error | Error
> C002 | OK | OK
> C003 | OK | Error
> C004 | Error | OK
You can use sub-query & do aggregation :
select t.campaign,
(case when buy_dt > sale_dt then 'OK' else 'Error' end) as Check_date,
(case when buy_qty > sale_qty then 'OK' else 'Error' end) as Check_qty
from (select s.campaign,
max(case when s.event_type = 'buy' then s.event_date end) as buy_dt,
max(case when s.event_type = 'sale' then s.event_date end) as sale_dt,
max(case when s.event_type = 'buy' then s.quantity end) as buy_qty,
max(case when s.event_type = 'sale' then s.quantity end) as sale_qty
from sales s
group by s.campaign
) t;
SELECT t1.campaign,
CASE WHEN t1.event_date > t2.event_date
THEN 'OK'
ELSE 'Error'
END Check_Date,
CASE WHEN t1.quantity > t2.quantity
THEN 'OK'
ELSE 'Error'
END Check_Quantity
FROM sales t1
JOIN sales t2 USING ( campaign )
WHERE t1.event_type = 'buy'
AND t2.event_type = 'sale';
fiddle
For the result to be meaningful you MUST add unique index by (campaign, event_type) and restrict values list safe for event_type field (make it ENUM?).
Also you have not specified what must be returned for a campaign when only one record for it exists - my query will not return such campaign at all.

SQL JOIN, GROUP BY on Four tables to get Record By Month

I have the following DB design. Tables are:
auth_user
----------
first_name
last_name
staffuser
----------
phone_number
user_id
billing_customerservicebill
-----------------------------
bill_id
service_provider_id
discounted_price
billing_billmanagement
------------------------
creation_date
My query return Sum of discounted_price each user by month row wise. I need every month record show in column.
The following query gives me This record
select a.service_provider_id, first_name, Sum(a.discounted_price), EXTRACT(MONTH FROM c.creation_date)
from billing_customerservicebill a
left outer join users_staffuser b
on a.service_provider_id = b.id
left outer join billing_billmanagement c
on a.bill_id = c.id
left outer join auth_user d
on d.id = b.user_id
where c.creation_date between '2017-11-01' AND '2017-12-31'
group by service_provider_id, first_name, EXTRACT(MONTH FROM c.creation_date)
order by 1
My data show in Table Currently
service_provider_id | first_name | Sum | Month
5 | suneel 31500 | 11
5 | Suneel | 900 | 12
Expected data is
service_provider_id | first_name | Nov | December
5 | suneel | 31500 | 900
The most flexible approach is to use conditional aggregation...
select
a.service_provider_id,
first_name,
SUM(CASE WHEN c.creation_date >= '2017-11-01' AND c.creation_date < '2017-12-01' THEN a.discounted_price END) AS nov,
SUM(CASE WHEN c.creation_date >= '2017-12-01' AND c.creation_date < '2018-01-01' THEN a.discounted_price END) AS dec
from billing_customerservicebill a
left outer join users_staffuser b
on a.service_provider_id = b.id
left outer join billing_billmanagement c
on a.bill_id = c.id
left outer join auth_user d
on d.id = b.user_id
where c.creation_date between '2017-11-01' AND '2017-12-31'
group by service_provider_id, first_name
order by 1
This shows that you need to know in advance which columns you're going to calculate.
Please, try with below solution it's near to your answer:
Where month as column and group by users:
select B.service_provider_id, B.first_name,
(case when month=1 then discounted_price else 0 end) as JAN,
(case when month=2 then discounted_price else 0 end) as FEB,
(case when month=3 then discounted_price else 0 end) as MAR,
(case when month=4 then discounted_price else 0 end) as APR,
(case when month=5 then discounted_price else 0 end) as MAY,
(case when month=6 then discounted_price else 0 end) as JUN,
(case when month=7 then discounted_price else 0 end) as JULY,
(case when month=8 then discounted_price else 0 end) as AUG,
(case when month=9 then discounted_price else 0 end) as SEP,
(case when month=10 then discounted_price else 0 end) as OCT,
(case when month=11 then discounted_price else 0 end) as NOV,
(case when month=12 then discounted_price else 0 end) as DEC
from(
select a.service_provider_id, first_name, Sum(a.discounted_price) as discounted_price, EXTRACT(MONTH FROM c.creation_date) as month
from billing_customerservicebill a
left outer join users_staffuser b
on a.service_provider_id = b.id
left outer join billing_billmanagement c
on a.bill_id = c.id
left outer join auth_user d
on d.id = b.user_id
where c.creation_date between '2017-11-01' AND '2017-12-31'
group by service_provider_id, first_name, EXTRACT(MONTH FROM c.creation_date)
) as B
group by B.service_provider_id, B.first_name

How do I combine 2 queries from difference tables

I have 2 queries. The 1st is to find the netamount and the 2nd is the sum of budget. i need to show the result including the store name and storenumber which are from difference table.
Here is my 1st Query to find total netamount:
SELECT sum(a.netamt) as netamt, b.store_name
FROM site_sales a JOIN site_store b ON b.storenum = a.storenum
WHERE a.busidate >= '2017-01-01' AND a.busidate <='2017-04-30'
GROUP BY a.storenum
The 2nd Query is to find total budget
SELECT
SUM(CASE WHEN c.busidate BETWEEN '2017-01' AND '2017-04' THEN c.budget ELSE 0 END) as budget,
b.store_name
FROM site_kpimthslsbgt c JOIN site_store b ON b.storenum = c.storenum
GROUP BY c.storenum
I need to combine both of this query. The result of output should be
like this
try this Also you need to put up join conditions in the end if there are multiple records fetched in your individual queries :
Select x.netamt, y.budget, y.store_name
from
(
SELECT sum(a.netamt) as netamt, b.store_name
FROM site_sales a JOIN site_store b ON b.storenum = a.storenum
WHERE a.busidate >= '2017-01-01' AND a.busidate <='2017-04-30'
GROUP BY a.storenum
) x
inner join
(
SELECT
SUM(CASE WHEN c.busidate BETWEEN '2017-01' AND '2017-04' THEN c.budget ELSE 0 END) as budget,
b.store_name
FROM site_kpimthslsbgt c JOIN site_store b ON b.storenum = c.storenum
GROUP BY c.storenum
) y
on x.store_name = y.store_name
SELECT sum(a.netamt) as netamt,SUM(CASE WHEN c.busidate BETWEEN '2017-01' AND '2017-04' THEN c.budget ELSE 0 END) as budget,b.store_name
FROM site_store b
JOIN site_kpimthslsbgt c ON b.storenum = c.storenum
JOIN site_sales a ON b.storenum = a.storenum
GROUP BY b.storenum
Try above query.
Hope this will help you.
I think there are 4 possible scenarios here, stores which have sales and budget, stores which have sales but no budget, stores which have budget but no sales, stores which have neither sales nor budget.
Given
DROP TABLE IF EXISTS site_sales,site_budget;
CREATE TABLE site_sales(ID INT auto_increment primary key, site_id int, busidate date,amt int);
create table site_budget(ID INT auto_increment primary key, site_id int, busidate date,amt int);
insert into site_sales (site_id,busidate,amt) values
(1,'2017-04-01',10),(1,'2017-04-01',20),
(2,'2017-04-01',10);
insert into site_budget (site_id,busidate,amt) values
(1,'2017-04-01',200),
(3,'2017-04-01',100);
This query
SELECT b.name,
sum(case when a.busidate between '2017-01-01' and '2017-04-30' then a.amt else 0 end) as netamt,
ifnull((select SUM(CASE WHEN c.busidate BETWEEN '2017-01-01' AND '2017-04-30' THEN c.amt ELSE 0 END) from site_budget c where b.id = c.site_id ),0) as budget
FROM sites b
left JOIN site_sales a ON b.id = a.site_id
GROUP BY b.id
result
+--------+--------+--------+
| name | netamt | budget |
+--------+--------+--------+
| Store1 | 30 | 200 |
| Store2 | 10 | 0 |
| Store3 | 0 | 100 |
| Store4 | 0 | 0 |
+--------+--------+--------+
4 rows in set (0.00 sec)

MySQL - users' monthly spend in the first 6 months of initial subscription purchase

I am trying to create report that shows subscription price (AmountPerMonth) month by month for 6 months for all users - where Month1 is the date the user has purchased the 1st subscription date (NOT the registration date) , and Month2 etc are the subsequent months from that date, varying for each account.
The format of the table for this report
I have managed to pull the first 2 months table, but can't figure out how to continue up to the 6th month. Thank you in advance!
SELECT F1.Id, F1.Month1, F2.Month2
FROM
(SELECT Id, AmountPerMonth AS Month1, ActionDate
FROM MONTLYSPEND
GROUP BY ID
HAVING MIN(ActionDate)) AS F1,
(SELECT t1.Id R, t2.AmountPerMonth AS Month2, MIN(t2.ActionDate)
FROM MONTLYSPEND t1
INNER JOIN MONTLYSPEND t2
ON t1.Id = t2.Id
AND t1.ActionDate < t2.ActionDate
GROUP BY t1.Id) AS F2
WHERE F1.id = F2.R
;
Turns out there were two ways - Stored Procedure, which takes too much memory, or using CASE WHEN, this pivots the table as well. Hope it's useful to people who have to generate reports showing various activity per user day by day or month by month on the x axis. The main difficulty I had was the fact that Month_1 (first purchased subscription) was a different date for every user. This report can be used to analyse your users behavior in the first 6 months of their subscription.
The report generated by this query looks like this:
+--------+----------+------------------+---------+---------+--------+
| UserId | Currency | FirstSubscrPurch | Month_1 | Month_2 | etc... |
+--------+----------+------------------+---------+---------+--------+
| 123 | GBP | 2010-05-27 | 34.00 | 27.00 | 0.00 |
+--------+----------+------------------+---------+---------+--------+
SELECT F6.USERID, F6.Currency, DATE_FORMAT(F6.FirstSubscrPurch, "%Y-%m-%d") AS FirstSubscrPurch, F6.MONTH_1, F6.MONTH_2,F6.MONTH_3, F6.MONTH_4, F6.MONTH_5, F6.MONTH_6, ROUND(((F6.MONTH_1+F6.MONTH_2+F6.MONTH_3+F6.MONTH_4+F6.MONTH_5+F6.MONTH_6)/6),2) AVERAGE, F6.CURRENCY
FROM (
SELECT
UserId, Currency, FirstSubscrPurch,
SUM(CASE WHEN YEAR_AND_MONTH_INDEX = 0 THEN TOTAL_AMOUNT_PAID ELSE 0 END) MONTH_1,
SUM(CASE WHEN YEAR_AND_MONTH_INDEX = 1 THEN TOTAL_AMOUNT_PAID ELSE 0 END) MONTH_2,
SUM(CASE WHEN YEAR_AND_MONTH_INDEX = 2 THEN TOTAL_AMOUNT_PAID ELSE 0 END) MONTH_3,
SUM(CASE WHEN YEAR_AND_MONTH_INDEX = 3 THEN TOTAL_AMOUNT_PAID ELSE 0 END) MONTH_4,
SUM(CASE WHEN YEAR_AND_MONTH_INDEX = 4 THEN TOTAL_AMOUNT_PAID ELSE 0 END) MONTH_5,
SUM(CASE WHEN YEAR_AND_MONTH_INDEX = 5 THEN TOTAL_AMOUNT_PAID ELSE 0 END) MONTH_6
FROM (
SELECT
hp.UserId, hp.Currency, MIN(hp.Date) AS FirstSubscrPurch,
CONCAT(YEAR(Date),'-',MONTH(Date)) AS YEAR_AND_MONTH,
TIMESTAMPDIFF( MONTH, CONCAT(YEAR(FIRST_PAYMENT_DATE),'-',MONTH(FIRST_PAYMENT_DATE),'-1'), CONCAT(YEAR(Date),'-',MONTH(Date),'-1')) AS YEAR_AND_MONTH_INDEX, -- generates string in format YYYY-M-D
MIN(Date) FIRST_PAYMENT_OF_MONTH,
MAX(Date) LAST_PAYMENT_OF_MONTH,
COUNT(*) NUM_PAYMENTS,
SUM(hp.Amount) TOTAL_AMOUNT_PAID,
SUM(hp.Credits) Credits
FROM payments hp
JOIN (
SELECT UserId, MIN(Date) FIRST_PAYMENT_DATE, ADDDATE(MIN(Date), INTERVAL 6 MONTH) SIX_MONTHS_AFTER_FIRST_PAYMENT
FROM payments hp
GROUP BY UserId
) USER_MIN_ID ON USER_MIN_ID.UserId = hp.UserId
AND hp.Date BETWEEN FIRST_PAYMENT_DATE AND CONCAT(YEAR(SIX_MONTHS_AFTER_FIRST_PAYMENT),'-',MONTH(SIX_MONTHS_AFTER_FIRST_PAYMENT),'-1')
GROUP BY UserId, Currency, YEAR_AND_MONTH
ORDER BY hp.UserId, hp.Date
) F
GROUP BY UserId, Currency
ORDER BY UserId DESC) F6;

SQL query - print the min and max from one table in the same row

I need a little help with an sql query.
I have a table with a format and data that looks like this:
id | applicant_id | application_status | status_time
1 | 1234 | received | 2013-05-06 15:00:00
1 | 1234 | pending | 2013-05-06 15:30:00
1 | 1234 | approved | 2013-05-06 16:00:00
The problem that I need to solve will have to print the following:
applicant_id | initial_status | initial_time | current_status | current_status_time
1234 | received | 2013-05-06 15:00:00 | approved | 2013-05-06 16:00:00
How could I go about accomplishing something like this, preferably only using joins and no nested selects?
The best way to approach this, in general, is to use the row_number() function. However, this requires a nested select:
select t.applicant_id,
max(case when seqnum_asc = 1 then status end) as initial_status,
max(case when seqnum_asc = 1 then status_time end) as initial_time,
max(case when seqnum_desc = 1 then status end) as current_status,
max(case when seqnum_desc = 1 then status_time end) as current_time
from (select t.*,
row_number() over (partition by applicant_id order by status_time) as seqnum_asc,
row_number() over (partition by applicant_id order by status_time desc) as seqnum_desc
from t
) t
group by t.applicant_id;
If your database did not support row_number(), I would recommend correlated subqueries, for readability. But those are also nested. Here is a solution in MySQL that meets your requirements:
select t.applicant_id,
substring_index(group_concat(status) separator ',' order by status_time), ',', 1) as initial_status,
min(status_time) as initial_time,
substring_index(group_concat(status) separator ',' order by status_time desc), ',', 1) as current_status,
max(status_time) as current_time
from t
group by t.applicant_id;
You did not state your database product, but you could use something like this on any database:
select t1.id,
t1.applicant_id,
max(case when t1.status_time = t2.mintime then t1.application_status end) initial_status,
max(case when t1.status_time = t2.mintime then t1.status_time end)initial_time,
max(case when t1.status_time = t2.maxTime then t1.application_status end) current_status,
max(case when t1.status_time = t2.maxTime then t1.status_time end) `current_time`
from yourtable t1
inner join
(
select id, applicant_id,
max(status_time) maxTime,
min(status_time) mintime
from yourtable
group by id, applicant_id
) t2
on t1.id = t2.id
and t1.applicant_id = t2.applicant_id
and
(
t1.status_time = t2.mintime
or t1.status_time = t2.maxtime
)
group by t1.id, t1.applicant_id;
See SQL Fiddle with Demo
SELECT a.application_id
, a.application_status as initial_status
, a.status_time as initial_time
, b.application_status as current_status
, b.status_time as current_status_time
FROM sample1 A
CROSS JOIN sample1 B
WHERE A.application_status = 'received'
and b. application_status = 'approved'
Assuming MS SQL (Transact-SQL), and that your source table is aptly named [SourceTable]. =)
SELECT DISTINCT
[Probe].applicant_id,
[LogMin].application_status [initial_status],
[LogMin].status_time [initial_time],
[LogMax].application_status [current_status],
[LogMax].status_time [current_status_time]
FROM (
SELECT MAX(status_time) [MaxDate],
MIN(status_time) [MinDate],
[applicant_id]
FROM [SourceTable]
GROUP BY [applicant_id]
) [Probe]
INNER JOIN [SourceTable] [LogMax]
ON [Probe].[applicant_id] = [LogMax].[applicant_id]
AND [Probe].[MaxDate] = [LogMax].[status_time]
INNER JOIN [SourceTable] [LogMin]
ON [Probe].[applicant_id] = [LogMin].[applicant_id]
AND [Probe].[MinDate] = [LogMin].[status_time]
Link to the SQLFiddle test is here.
Assuming that for one applicant_id you have one line for 'received' status and also one line for 'approved' status (as you listed in the question) you can use inline views to solve your issue:
select section1.applicant_id AS applicant_id, 'received' AS initial_status,
section1.status_time AS initial_time, 'approved' AS current_status,
section2.status_time AS current_status_time from
(select applicant_id, status_time from yourtable where application_status = 'received') section1,
(select applicant_id, status_time from yourtable where application_status = 'approved') section2
where section1.applicant_id = section2.applicant_id;
Try something like this.
select
t1.applicant_id,
t2.application_status initial_status,
t1.initial_time,
t3.application_status current_status,
t1.current_status_time
from
(select
applicant_id,
min(status_time) initial_time,
max(status_time) current_status_time
from
your_table
group by
applicant_id) t1
inner join your_table t2
on (t1.applicant_id = t2.applicant_id and t1.initial_time = t2.status_time)
inner join your_table t3
on (t1.applicant_id = t3.applicant_id and t1.current_status_time = t3.status_time)