MySQL - join query results vertically - mysql

I am trying to create a summary report for to capture daily stats. Basically I need the outcome similar to:
Table_Name Updated_Rows Created_Rows Date
Table 1 10 5 2019-04-23
Table 2 17 55 2019-04-23
Now I can fetch the individual values using basic commands:
select count(*) as created_rows
from accounts
where date(updated_at) = date(now())
and
select count(*) as created_rows
from accounts
where date(created_at) = date(now())
Can also combine the data using the UNINON ALL,
(SELECT table_name FROM information_schema.tables where table_name='accounts')
UNION ALL
(select count(*) as created_rows from accounts where date(created_at) = date(now()))
UNION ALL
(select count(*) as updated_rows from accounts where date(updated_at) = date(now()))
However the output I get is kind of stacked vertically and I wish to retain the labels/column names and would want to add data row by row for all the tables I want to assess.
I am sure there is an easier way but I can't seem the find a way out to get this done. Don't need the final query, just help me with a direction to look towards.

For a single table, you can do the following:
SELECT 'account' AS 'Table_Name'
, SUM(date(updated_at) = date(now())) 'Updated_Rows'
, SUM(date(created_at) = date(now())) 'Created_Rows'
, date(now()) AS 'Date'
FROM accounts
where SUM(date(updated_at) = date(now())) is basically the same as
IF(SUM(date(updated_at) = date(now())), 1, 0)
Then UNION ALL result from other tables with the similar query.

You could try something like the following (untested):
select '#x' as table_name,
(select count(*) FROM #x and date(created_at) = date(now())) as created_rows,
(select count(*) FROM #x and date(updated_at) = date(now())) as updated_rows
As part of a prepared statement (https://dev.mysql.com/doc/refman/5.6/en/sql-syntax-prepared-statements.html) or proc (https://medium.com/#peter.lafferty/mysql-stored-procedures-101-6b4fe230967) or just union to get multiple tables (note that you'd have to change some syntax). I'm not sure how you need to run this, so I'm not sure what exactly you need, what kind of performance you're after, if you're just going to make a script and manually select the tables or if you're trying to run this on all tables, etc.
EDIT: jxc's query is much better than mine!

Is this what you are looking for? If so, make sure to change the table names in the subsequent UNION ALL queries.
SELECT
'accounts' AS TableName,
SUM(DATE(updated_at) = DATE(NOW())) AS updated_rows,
SUM(DATE(created_at) = DATE(NOW())) AS created_rows,
DATE(NOW()) AS `Date`
FROM
accounts
UNION ALL
SELECT
'accounts2' AS TableName,
SUM(DATE(updated_at) = DATE(NOW())) AS updated_rows,
SUM(DATE(created_at) = DATE(NOW())) AS created_rows,
DATE(NOW()) AS `Date`
FROM
accounts2
and so forth....
EDIT This query is identical to jxc's query posted earlier

Related

order by with union in SQL is not working

Is it possible to order when the data comes from many select and union it together? Such as
In this statement, the vouchers data is not showing in the same sequence as I saved on the database, I also tried it with "ORDER BY v_payments.payment_id ASC" but won't be worked
( SELECT order_id as id, order_date as date, ... , time FROM orders WHERE client_code = '$searchId' AND order_status = 1 AND order_date BETWEEN '$start_date' AND '$end_date' ORDER BY time)
UNION
( SELECT vouchers.voucher_id as id, vouchers.payment_date as date, v_payments.account_name as name, ac_balance as oldBalance, v_payments.debit as debitAmount, v_payments.description as descriptions,
vouchers.v_no as v_no, vouchers.v_type as v_type, v_payments.credit as creditAmount, time, zero as tax, zero as freightAmount FROM vouchers INNER JOIN v_payments
ON vouchers.voucher_id = v_payments.voucher_id WHERE v_payments.client_code = '$searchId' AND voucher_status = 1 AND vouchers.payment_date BETWEEN '$start_date' AND '$end_date' ORDER BY v_payments.payment_id ASC , time )
UNION
( SELECT return_id as id, return_date as date, ... , time FROM w_return WHERE client_code = '$searchId' AND w_return_status = 1 AND return_date BETWEEN '$start_date' AND '$end_date' ORDER BY time)
Wrap the sub-select queries in the union within a SELECT
SELECT id, name
FROM
(
SELECT id, name FROM fruits
UNION
SELECT id, name FROM vegetables
)
foods
ORDER BY name
If you want the order to only apply to one of the sub-selects, use parentheses as you are doing.
Note that depending on your DB, the syntax may differ here. And if that's the case, you may get better help by specifying what DB server (MySQL, SQL Server, etc.) you are using and any error messages that result.
You need to put the ORDER BY at the end of the statement i.e. you are ordering the final resultset after union-ing the 3 intermediate resultsets
To use an ORDER BY or LIMIT clause to sort or limit the entire UNION result, parenthesize the individual SELECT statements and place the ORDER BY or LIMIT after the last one. See link below:
ORDER BY and LIMIT in Unions
(SELECT a FROM t1 WHERE a=10 AND B=1)
UNION
(SELECT a FROM t2 WHERE a=11 AND B=2)
ORDER BY a LIMIT 10;

LEAST and GREATEST date value from multiple tables, laravel way

Is there any "Laravel-ish" way to capture the earliest and latest dates from multiple date fields in several laravel tables?
table for model1:
fielda| fieldb| date1
table2 for model2:
fieldc| fieldd| date1| date2
I couldn't find any elegant way to do it. My current approach is something like:
$model1_dates = Model1::select(\DB::raw('MIN(date1) as min_date'), \DB::raw('MAX(date1) as max_date'))->get();
$model2_dates = Model2::select(\DB::raw('MIN(date2) as min_date'), \DB::raw('MAX(date2) as max_date'))->get();
$model2_dates2 = Model2::select(\DB::raw('MIN(date2) as min_date'), \DB::raw('MAX(date1) as max_date'))->get();
afterwards, I will compare the results and get the minimum and maximum of those...
What approaches would be better?
I've used Laravel, but I'm not a fan - So I don't really know "Laravel-ish" or what it is. If you mean "something that looks simple", then it could be
$minDate = collect([
Model1::min('date1'),
Model2::min('date1'),
Model2::min('date2'),
])->min();
$maxDate = collect([
Model1::max('date1'),
Model2::max('date1'),
Model2::max('date2'),
])->max();
That's neat - But that are 6 queries to the DB.
A single raw SQL would be
select min(min_date) as min_date, max(max_date) as max_date
from (
select min(date1) as min_date, max(date1) as max_date from table1
union all
select min(date1) as min_date, max(date1) as max_date from table2
union all
select min(date2) as min_date, max(date2) as max_date from table2
) x
or
select min(min_date) as min_date, max(max_date) as max_date
from (
select min(date1) as min_date, max(date1) as max_date
from table1
union all
select min(least(date1, date2)) as min_date,
max(greatest(date1, date2)) as max_date
from table2
) x
depending on what indexes you have. The first query performs better, when every date column has its own index.
Yes there are functions like max()and min(). Also you can unite two queries with the union() method
Aggregate Methods
Union
And you can also use orderBy with laravel. Works also on dates.
OrderBy
Fastest way:
$minDateModel1 = Model1::min('date1');
$maxDateModel1 = Model1::max('date1');
Or:
$minDateModel1 = Model1::oldest('date1')->first()->pluck('date1');
$maxDateModel1 = Model1::latest('date1')->first()->pluck('date1');
Or:
$minDateModel1 = Model1::max('date1');
//should be the oldest first, newest at last
$minDateModel1 = Model1::orderBy('date1', 'asc')->first()->pluck('date1');
$maxDateModel1 = Model1::orderBy('date1', 'asc')->first()->pluck('date1');
Should do the trick:
$minDateModel1 = Model1::max('date1');
$minDateModel21 = Model2::max('date1');
$minDateModel22 = Model2::max('date2');
$allMinDates = $allMinDates->merge($minDateModel1);
$allMinDates = $allMinDates->merge($minDateModel21);
$allMinDates = $allMinDates->merge($minDateModel22);
$min = $allMinDates->min();

SQL query that finds a negative change between two rows with the same name field

I have a single table with rows like this: (Date, Score, Name)
The Date field has two possible dates, and it's possible that a Name value will appear under only one date (if that name was recently added or removed).
I'm looking to get a table with rows like this: (Delta, Name), where delta is the score change for each name between the earlier and later dates. In addition, only a negative change interests me, so if Delta>=0, it shouldn't appear in the output table at all.
My main challenge for me is calculating the Delta field.
As stated in the title, it should be an SQL query.
Thanks in advance for any help!
I assumed that each name can have it's own start/end dates. It can be simplified significantly if there are only two possible dates for the entire table.
I tried this out in SQL Fiddle here
SELECT (score_end - score_start) delta, name_start
FROM
( SELECT date date_start, score score_start, name name_start
FROM t t
WHERE NOT EXISTS
( SELECT 1
FROM t x
WHERE x.date < t.date
AND x.name = t.name
)
) AS start_date_t
JOIN
( SELECT date date_end, score score_end, name name_end
FROM t t
WHERE NOT EXISTS
( SELECT 1
FROM t x
WHERE x.date > t.date
AND x.name = t.name
)
) end_date_t ON start_date_t.name_start = end_date_t.name_end
WHERE score_end-score_start < 0
lets say you have a table with date_value, sum_value
Then it should be something like that:
select t.date_value,sum_value,
sum_value - COALESCE((
select top 1 sum_value
from tmp_num
where date_value > t.date_value
order by date_value
),0) as sum_change
from tmp_num as t
order by t.date_value
The following uses a "trick" in MySQL that I don't really like using, because it turns the score into a string and then back into a number. But, it is an easy way to get what you want:
select t.name, (lastscore - firstscore) as diff
from (select t.name,
substring_index(group_concat(score order by date asc), ',', 1) as firstscore,
substring_index(group_concat(score order by date desc), ',', 1) as lastscore
from table t
group by t.name
) t
where lastscore - firstscore < 0;
If MySQL supported window functions, such tricks wouldn't be necessary.

Catching latest column value change in SQL

How can I get the date for the latest value change in one column with one SQL query?
Possible database situation:
Date State
2012-11-25 state one
2012-11-26 state one
2012-11-27 state two
2012-11-28 state two
2012-11-29 state one
2012-11-30 state one
So result should return 2012-11-29 as latest change state. If I group by State value, I will get the date for first time I have that state in database.
The query will group the table on state and show the state and in the date field the latest date created of that state.
From the given input the output would be
Date State
2012-11-30 state one
2012-11-28 state two
This will get you the last state:
-- Query 1
SELECT state
FROM tableX
ORDER BY date DESC
LIMIT 1 ;
Encapsulating the above, we can use it to get the date just before the last change:
-- Query 2
SELECT t.date
FROM tableX AS t
JOIN
( SELECT state
FROM tableX
ORDER BY date DESC
LIMIT 1
) AS last
ON last.state <> t.state
ORDER BY t.date DESC
LIMIT 1 ;
And then use that to find the date (or the whole row) where the last change occurred:
-- Query 3
SELECT a.date -- can also be used: a.*
FROM tableX AS a
JOIN
( SELECT t.date
FROM tableX AS t
JOIN
( SELECT state
FROM tableX
ORDER BY date DESC
LIMIT 1
) AS last
ON last.state <> t.state
ORDER BY t.date DESC
LIMIT 1
) AS b
ON a.date > b.date
ORDER BY a.date
LIMIT 1 ;
Tested in SQL-Fiddle
And a solution that uses MySQL variables:
-- Query 4
SELECT date
FROM
( SELECT t.date
, #r := (#s <> state) AS result
, #s := state AS prev_state
FROM tableX AS t
CROSS JOIN
( SELECT #r := 0, #s := ''
) AS dummy
ORDER BY t.date ASC
) AS tmp
WHERE result = 1
ORDER BY date DESC
LIMIT 1 ;
I believe this is the answer:
SELECT
DISTINCT State AS State, `Date`
FROM
Table_1 t1
WHERE t1.`Date`=(SELECT MAX(`Date`) FROM Table_1 WHERE State=t1.State)
...and the test:
http://sqlfiddle.com/#!2/8b0d8/5
If you add another column 'changed datetime' you can fill this using an update trigger that inserts NOW(). If you query your table ordering on the changed column, it will endup first.
CREATE TRIGGER `trigger` BEFORE UPDATE ON `table`
FOR EACH ROW
BEGIN
SET ROW.changed = NOW();
END$$
Try this ::
Select
MAX(`Date`), state from mytable
group by state
If you had been using postgres, you could compare different rows in the same table using "LEAD .. OVER" I have not managed to find the same functionallity in mysql.
A bit hairy, but I think this will do:
select min(t1.date) from table_1 t1 where
(select count(distinct state) from table_1 where table_1.date>=t1.date)=1
Basically, this asks for the first time no changes in state is found for any later values. Be warned, it may be this query scales terribly for large data sets....
I think your best choice here are analytical functions. Try this - it should be OK performance-wise:
SELECT *
FROM test
WHERE my_date = (SELECT MAX (my_date)
FROM (SELECT MY_DATE
FROM ( SELECT MY_DATE,
STATE,
LAG (state) OVER (ORDER BY MY_DATE)
lag_val
FROM test
ORDER BY MY_DATE) a
WHERE state != lag_val))
In the inner select, the LAG function gets the previous value in the STATE column and in the outer select I mark the date of a change - those with lag value different than the current state value. And outside, I'm getting the latest date from those dates of a change... I hope that this is what you needed.
SELECT MAX(DATE) FROM YOUR_TABLE
Above answer doesn't seem to satisfy what OP needs.
UPDATED ANSWER WITH AFTER INSERT/UPDATE TRIGGER
DELCARE #latestState varchar;
DELCARE #latestDate date;
CREATE TRIGGER latestInsertTrigger AFTER INSERT ON myTable
FOR EACH ROW
BEGIN
IF OLD.DATE <> NEW.date THEN
SET #latestState = NEW.state
SET #latestDate = NEW.date
END IF
END
;
CREATE TRIGGER latestUpdateTrigger AFTER UPDATE ON myTable
FOR EACH ROW
BEGIN
IF OLD.DATE = NEW.date AND OLD.STATE <> NEW.STATE THEN
SET #latestState = NEW.state
SET #latestDate = NEW.date
END IF
END
;
You may use the following query to get the latest record added/updated:
SELECT DATE, STATE FROM myTable
WHERE STATE = #latestState
OR DATE = #latestDate
ORDER BY DATE DESC
;
Results:
DATE STATE
November, 30 2012 00:00:00+0000 state one
November, 28 2012 00:00:00+0000 state two
November, 27 2012 00:00:00+0000 state two
The above query results needs to be limitted to 2, 3 or n based on what you need.
Frankly it seems like you want to get max from both columns based on the data sample you have given. Assuming that your state only increases with the date. Only I wish if the state was an integer :D
Then union of two max sub queries on both columns would have solved it easily. Still a string manipulation regex can find what's the max in state column. Finally this approach needs limit x. However it still has lope hole. Anyway it took me sometime to figure your need out :$

Performance Issue On MySQL DataBase

I have table contains around 14 million records, and I have multiple SP's contain Dynamic SQL, and these SP's contain multiple parameters,and I build Indexes on my table, but the problem is I have a performance Issue, I tried to get the Query from Dynamic SQL and run it, but this query takes between 30 Seconds to 1 minute, my query contains just select from table and some queries contain join with another table with numeric values in where statement and grouping and order by.
I checked status result, I found the grouping by takes all time, and I checked Explain result, It's using right index.
So what I should doing to enhance my queries performance.
Thanks for your cooperation.
-- EDIT, Added queries directly into question instead of comment.
SELECT
CONCAT(column1, ' - ', column1 + INTERVAL 1 MONTH) AS DateRange,
cast(SUM(column2) as SIGNED) AS Alias1
FROM
Table1
INNER JOIN Table2 DD
ON Table1.Date = Table2.Date
WHERE
Table1.ID = 1
AND (Date BETWEEN 20110101 AND 20110201)
GROUP BY
MONTH(column1)
ORDER BY
Alias1 ASC
LIMIT 0, 10;
and this one:
SELECT
cast(column1 as char(30)) AS DateRange,
cast(SUM(column2) as SIGNED)
FROM
Table1
INNER JOIN Table2 DD
ON Table1.Date = Table2.Date
WHERE
Table1.ID = 1
AND (Date BETWEEN 20110101 AND 20110102)
GROUP BY
column1
ORDER BY
Alias1 ASC
LIMIT 0, 10;
For this query:
SELECT
CONCAT(column1, ' - ', column1 + INTERVAL 1 MONTH) AS DateRange <<--error? never mind
, cast(SUM(column2) as SIGNED)
FROM Table1
INNER JOIN Table2 DD ON Table1.Date = Table2.Date
WHERE Table1.ID = 1
AND (Date BETWEEN 20110101 AND 20110201)
GROUP BY MONTH(column1) <<-- problem 1.
ORDER BY column2 ASC <<-- problem 2.
LIMIT 0, 10;
If you group by a function MySQL cannot use an index. You can speed this up by adding an extra column YearMonth to the table1 that contains the year+month, put an index on that and then group by yearmonth.
The order by does not make sense. You are adding column2, ordering by that column serves no purpose. If you order by yearmonth asc the query will run much faster and make more sense.