Eloquent SELECT from UNION result - php

I have two different tables with the (almost) the same structure:
(not the best DB design, but unfortunately out of my control)
Table A:
+-----------+------+-------+-----+
| client_id | year | month | fee |
+-----------+------+-------+-----+
| 33 | 11 | 2022 | 11 |
| 42 | 11 | 2022 | 13 |
| 33 | 12 | 2022 | 27 |
| 16 | 12 | 2022 | 15 |
+-----------+------+-------+-----+
Table B:
+-----------+------+-------+-----+
| client_id | year | month | fee |
+-----------+------+-------+-----+
| 33 | 11 | 2022 | 19 |
| 57 | 11 | 2022 | 34 |
+-----------+------+-------+-----+
I want to SUM fees of all clients grouping bu month and year.
For one table the eloquent query is simple:
$queryA = DB::table('A')
->selectRaw('month, year, COUNT(*) AS total, SUM(fee) AS total_fee')
->groupBy(['month', 'year']);
($queryB will be the same, with ->table('B') instead ).
In order to get total sum of both tables, I want to use UNION.
The approach:
I want to apply a SELECT query on the result returned by UNION:
SELECT month, year, COUNT(*) AS total, SUM(total_fee) AS total_fee
FROM
(
(
SELECT month, year, COUNT(*) AS total, SUM(fee) AS total_fee
FROM A
GROUP BY month, year
) UNION (
SELECT month, year, COUNT(*) AS total, SUM(fee) AS total_fee
FROM B
GROUP BY month, year
)
) AS result_table
GROUP BY month, year
ORDER BY year DESC, month DESC;
The problem:
I tried this:
$queryA
->union($queryB)
->select(month, year, COUNT(*) AS total, SUM(total_fee) AS total_fee)
->groupBy(['month', 'year'])
the new SELECT statement is inserted into the $queryA sub-query.
Any idea how to achieve what I am looking for?

Related

How to use subquery with group by in mysql?

I have one table with multiple rows for particular users.i am having
data with many years like 2019,2018,2016 and more. i have two
scenarios:
1.i want data of particular INVOICE_YEAR.
2,but same time i want FIRST DATE OF INVOICE GENERATED FOR PARTICULAR
USER.
my sql query :
$yearOf this is dynamic year input variable.
$yearOf = 2019;
$Records = "SELECT MIN(inv.INVOICE_DATE) AS MIN_INVOICE_DATE
FROM invoices as inv
LEFT JOIN customers as cm ON cm.CUSTOMER_ID = inv.CUSTOMER_ID
where inv.INVOICE_YEAR IN (".$yearOf.")
group by inv.CUSTOMER_ID ORDER BY cm.CUSTOMER_NAME ASC";
As you can see my query if i want data of all users where INVOICE_YEAR IN ('2019').My first condition will satisfied i will get all data of users where INVOICE_YEAR = 2019.
But same time i want first invoice date so for this i used MIN(inv.INVOICE_DATE) but as i used
where inv.INVOICE_YEAR IN (".$yearOf.") this in where condition so it gives me first invoice date of particular year.
But i want first invoice date from whole table for all users.
I tried with subquery but it shows me error of Subquery returns more than 1 row
My query with subquery:
$Records = "SELECT
(
SELECT MIN(inv.INVOICE_DATE) AS MIN_INVOICE_DATE FROM invoices AS inv GROUP BY inv.CUSTOMER_ID) AS MIN_INVOICE_DATE
FROM invoices as inv
LEFT JOIN customers as cm ON cm.CUSTOMER_ID = inv.CUSTOMER_ID
where inv.INVOICE_YEAR IN (".$yearOf.")
group by inv.CUSTOMER_ID ORDER BY cm.CUSTOMER_NAME ASC";
For eg:
3 customers are there 101,102,103
data in table is like:
id | customer_id | invoice_date | invoice_year
1 | 101 | 2019-01-01 | 2019
2 | 101 | 2016-01-01 | 2016
3 | 101 | 2017-01-01 | 2017
4 | 101 | 2016-01-02 | 2016
5 | 102 | 2019-01-02 | 2019
6 | 103 | 2018-01-02 | 2018
7 | 103 | 2019-01-07 | 2019
8 | 102 | 2015-01-02 | 2015
As i Request query to get data of INVOICE_YEAR 2019 with first invoice date of particular user so it should give output like :
id | customer_id | invoice_date | invoice_year | min_invoice_date
1 | 101 | 2019-01-01 | 2019 | 2016-01-01
5 | 102 | 2019-01-02 | 2019 | 2015-01-02
7 | 103 | 2019-01-07 | 2019 | 2019-01-07
With as i want first invoice date IN COLUMN MIN_INVOICE_DATE of all users.
But it shows me data like :
id | customer_id | invoice_date | invoice_year | min_invoice_date
1 | 101 | 2019-01-01 | 2019 | 2019-01-01
5 | 102 | 2019-01-02 | 2019 | 2019-01-02
7 | 103 | 2019-01-07 | 2019 | 2019-01-07
You want to pull out the earliest invoice for each customer in 2019, along with the date of their earliest invoice within the whole table.
In MySQL 8.0, you can solve this using window functions:
SELECT id, customer_id, invoice_date, invoice_year, min_invoice_date
FROM (
SELECT
t.*,
ROW_NUMBER() OVER(PARTITION BY customer_id, invoice_year ORDER BY invoice_date) rn,
MIN(invoice_date) OVER(PARTITION BY customer_id) min_invoice_date
FROM mytable t
) x
WHERE invoice_year = 2019 AND rn = 1
In earlier versions, you can:
JOIN the table with a query that computes the overall minimum invoice_date per customer
use a correlated subquery with a NOT EXISTS condition to filter on the earliest invoice par customer in 2019
Query:
SELECT t.id, t.customer_id, t.invoice_date, t.invoice_year, m.min_invoice_date
FROM mytable t
INNER JOIN (
SELECT customer_id, MIN(invoice_date) min_invoice_date
FROM mytable
GROUP BY customer_id
) m ON m.customer_id = t.customer_id
WHERE
t.invoice_year = 2019
AND NOT EXISTS (
SELECT 1
FROM mytable t1
WHERE t1.invoice_year = 2019
AND t1.customer_id = t.customer_id
AND t1.invoice_date < t.invoice_date
)
In this demo on DB Fiddle, both queries return:
| id | customer_id | invoice_date | invoice_year | min_invoice_date |
| --- | ----------- | ------------ | ------------ | ---------------- |
| 1 | 101 | 2019-01-01 | 2019 | 2016-01-01 |
| 5 | 102 | 2019-01-02 | 2019 | 2015-01-02 |
| 7 | 103 | 2019-01-07 | 2019 | 2018-01-02 |
I guess you want the to see the details of the first (earliest-dated) invoice for each customer in each calendar year. You want that result filtered to cover only one year.
So, start with a subquery to find the date of the first invoice for every customer in each calendar year. (https://www.db-fiddle.com/f/bmBZ14Vr9Re6ahpfs2FF2X/0)
SELECT MIN(invoice_date) first_invoice_date,
YEAR(invoice_date) calendar_year,
customer_id
FROM invoices
GROUP BY YEAR(invoice_date), customer_id
Then retrieve the detail for those invoices by JOINing that subquery to your original invoices table. (https://www.db-fiddle.com/f/bmBZ14Vr9Re6ahpfs2FF2X/1)
SELECT invoices.*
FROM invoices
JOIN (
SELECT MIN(invoice_date) first_invoice_date,
YEAR(invoice_date) calendar_year,
customer_id
FROM invoices
GROUP BY YEAR(invoice_date), customer_id
) firsts
ON invoices.customer_id = firsts.customer_id
AND invoices.invoice_date = firsts.first_invoice_date
ORDER BY invoices.customer_id,
invoices.invoice_year,
invoices.invoice_date
Then, throw in WHERE invoices.invoice_year = 2019 to get just the year you want.
Notice that your invoice_year column is unnecessary, since it can always be computed from YEAR(invoice_date). You should consider getting rid of it.

get 12 month application count form SQL and start count form previous 3 month

This my Table Structure
###Table ###Expected Outpoot
ID | create_date Month | Application | Year
------------------------------ --------------------------
1 | 2017-06-25 10:00:11 10 | 0 | 2016
2 | 2017-06-26 10:00:11 11 | 0 | 2016
3 | 2017-07-02 10:00:11 12 | 0 | 2016
4 | 2017-07-25 10:00:11 1 | 0 | 2017
5 | 2017-08-21 10:00:11 2 | 0 | 2017
6 | 2017-08-22 10:00:11 3 | 0 | 2017
7 | 2017-08-25 10:00:11 4 | 0 | 2017
5 | 0 | 2017
6 | 2 | 2017
7 | 2 | 2017
8 | 3 | 2017
9 | 0 | 2017
I am Trying to get the monthly data count from my table. I want to start the count form Previous 3 month.
This is what I have tried so far?
Edit: I am close to solve the issue.Here is the query and result.
select date_format(tn.create_date,'%Y-%m') as mon,
count(*) as num
FROM table_name as tn
GROUP BY mon order by mon;
Month | Application
--------------------------------
2017-06 | 2
2017-07 | 2
2017-08 | 3
So, How do i get my expected output?
Here is the Query Fiddle
You need a list of months to join into your query. You can make a table for it, as #JohnHC suggested or use this hack to list the months like this:
set #start='2017-06-01';
select YEAR(date), MONTH(date), COUNT(create_date) from
(
select adddate(#start, INTERVAL #num:=#num+1 MONTH) date
from test, (select #num:=-9) num
limit 12
) as dt
LEFT JOIN test ON MONTH(create_date) = MONTH (date) and YEAR(create_date) = YEAR(date)
GROUP BY date;
Notice, that in the dt query, the actual data from the test table is not accessed, but it is required to contain at least 12 rows to work. Also, you don't need to use the same table for generating the month sequence what you use for querying the create_date.
You can set the start of the interval in the #num:=-9 expression. -9 in this case means 9 months before #start date. You can set the length of the interval in the LIMIT clause.
Example fiddle.

Mysql count and group between specific dates

I have a problem with a query.
I have a table like this:
tbl_people
id | startDate | endDate | Gender
1 | 2010-03-01 | 2011-04-11 | m
2 | 2010-04-01 | 2010-06-14 | f
3 | 2010-03-01 | 2010-04-11 | m
4 | 2009-09-09 | 2009-10-10 | f
For all years given in the database I want to count the gender of the people, for who that year is between startDate and endDate. When startDate is 2010 and endDate 2011 it should count for both.
So the result should look like this:
year | m | f | m+f
2009 | 0 | 1 | 1
2010 | 2 | 1 | 3
2011 | 1 | 0 | 1
I have no really good idea how to realize that query for a list of all years.
Currently I have this:
select
sum(case tbl_people.Gender when 'm' then 1 else 0 end),
sum(case tbl_people.Gender when 'f' then 1 else 0 end),
count( tbl_people.Gender )
...
Best regards
You need to join with a table that contains all the years. You can either create a real table with all the years, or construct it on the fly in a subquery:
SELECT y.year,
SUM(p.Gender = 'm') AS m,
SUM(p.Gender = 'f') AS f,
COUNT(*) AS `m+f`
FROM (SELECT 2009 AS year
UNION
SELECT 2010 AS year
UNION
SELECT 2011 AS year) AS y
LEFT JOIN tbl_people AS p ON y.year BETWEEN YEAR(p.startDate) AND YEAR(p.endDate)
GROUP BY y.year
DEMO

mysql select all unique rows in one column and all max rows in another column by datetime

What I need is to get the most recent (by date_time) unique player_id for each table_id
Table:
buyin_id player_id table_id date_time
---------|-----------|----------|--------------------|
1 | 10 | 21 | 2015-01-26 00:00:01
2 | 11 | 21 | 2015-01-26 00:00:02
3 | 12 | 21 | 2015-01-26 00:00:03
4 | 10 | 21 | 2015-01-26 00:00:04
5 | 11 | 21 | 2015-01-26 00:00:05
6 | 12 | 22 | 2015-01-26 00:00:06
7 | 13 | 22 | 2015-01-26 00:00:07
8 | 13 | 22 | 2015-01-26 00:00:08
Desired result:
buyin_id player_id table_id date_time
---------|-----------|----------|--------------------|
3 | 12 | 21 | 2015-01-26 00:00:03
4 | 10 | 21 | 2015-01-26 00:00:04
5 | 11 | 21 | 2015-01-26 00:00:05
6 | 12 | 22 | 2015-01-26 00:00:06
8 | 13 | 22 | 2015-01-26 00:00:08
I tried something like this which returns only 1 row instead of 1 row per table_id
SELECT pb.buyin_id, pb.player_id, pb.buyin, pb.cashout, pb.cashout_error, pb.date_time
FROM poker_buyin AS pb
INNER JOIN (SELECT player_id, MAX(date_time) AS MaxDateTime
FROM poker_buyin GROUP BY player_id) groupedpb
ON pb.player_id = groupedpb.player_id
AND pb.date_time = groupedpb.MaxDateTime
WHERE pb.player_id = '$player_id'";
The query you've mentioned finds the most recent record for each player id. And then you filter it to find just one player, so you get one row.
If you want to find the most recent record for each player and table, your inner query needs to be this:
SELECT MAX(buyin_id) buyin_id
FROM poker_buyin
GROUP BY player_id, table_id
This will get the row ids from your table that represent the latest player / table combinations.
Then you use that to pull records from your table, like this (http://sqlfiddle.com/#!2/be68b7/2/0)
SELECT whatever_columns
FROM poker_buyin
WHERE buyin_id IN
(
SELECT MAX(buyin_id) buyin_id
FROM poker_buyin
GROUP BY player_id, table_id
)
WHERE player_id = '$player_id'
ORDER BY player_id, table_id
There's a little trick in this query: The buyin_id value continually goes up, so it's a nice way of selecting the latest-in-time records for each combination.
if you don't need buyin_id in result columns that is simple:
SELECT DISTINCT player_id, table_id, max(date_time) as dt
FROM `poker_buyin `
GROUP BY player_id, table_id

MySQL query to order records ORDER BY

This question is related to my previous question - MySQL query to show records with current date on top and others as per descending order . The query i uses now is,
SELECT b.sales_id,b.category_id,b.sale_starts,b.sale_ends
FROM tbl_sales b WHERE b.active=1
UNION
SELECT b.sales_id,b.category_id,b.sale_starts,b.sale_ends
FROM tbl_sales b INNER JOIN tb_category c ON b.category_id=c.cat_id
WHERE c.cat_keyword LIKE 'a'
ORDER BY IF(sale_ends = DATE(NOW()), 0, 1), sale_ends DESC
and the results returned is as follows,
sales_id | category_id |sale_starts | sale_ends
----------|---------------------|------------|--------------
4 | 12 | 2012-04-05 | 2012-04-11 (today's date)
1 | 10 | 2012-03-31 | 2012-04-30
2 | 11 | 2012-03-22 | 2012-04-27
3 | 25 | 2012-03-31 | 2012-04-25
5 | 18 | 2012-04-05 | 2012-04-09
6 | 20 | 2012-02-23 | 2012-02-27
7 | 14 | 2012-02-25 | 2012-02-26
But now i am stuck with another issue and i need the to sort the records like shown below -
sales_id | category_id |sale_starts | sale_ends
----------|---------------------|------------|--------------
4 | 12 | 2012-04-05 | 2012-04-11 (today's date)
3 | 25 | 2012-03-31 | 2012-04-25
2 | 11 | 2012-03-22 | 2012-04-27
1 | 10 | 2012-03-31 | 2012-04-30
7 | 14 | 2012-02-25 | 2012-02-26 (expired/past dates)
6 | 20 | 2012-02-23 | 2012-02-27
5 | 18 | 2012-04-05 | 2012-04-09
I had tried using ASC instead of DESC in the query but then the expired dates are listed just after the today's date. I need the future dates to listed after the today's date and after that only the expired dates. How can this be implemented?
Need help. Thanks in advance
ORDER BY will process each condition in order from left to right. So if the ordering you need is something like "Any entries with today's date, then any entries in the future in ascending order of date, then other entries in ascending order of date" you can do something like this
ORDER BY (sale_ends=CURDATE()) DESC,(sale_ends>CURDATE()) DESC,sale_ends ASC
The reason the first two are listed as DESC is that the conditions will evaluate to one if true and zero if false. Since you want the true conditions first then you need to order them in DESCending order.
I might be misunderstanding something, but isn't this what you're looking for?
order by sale_ends < curdate(), sale_ends
Fiddle here.

Categories