I have to design an optimized MySQL query for generating a report based on 2 tables.
I have 2 tables one for services and another for payments. I accept user specific criteria for services and based on that I have to report services and corresponding payments. These transactions from 2 different tables will be in order of service dates and corresponding payments in order of payment dates. Along with that, I also have to report any advance payments paid on account (in database terms payments not linked to any particular service)
Currently I run one query for selecting services and unlinked payments using UNION of 2 tables based on given criteria. Then I run separate query for each service related payment through a loop.
Is there any way I can get all these transactions via a single query and that too in desired order.
Here are the relevant columns of 2 tables.
service table
id (PK)
account_no
date
service_amount
tran_type
payment table
id
account_no
date
pmt_amount
service_id (FK to service table nulls acceptable)
tran_type
Here are the queries I am trying
Query 1
select account_no, id, date, service_amount, tran_type
from service where <user specified criteria like date range>
UNION
select account_no, id, date, pmt_amount, tran_type
from payment where service_id is null and
<user specified criteria like date range>
order by date
Query2
This query is run on individual services on result of above query ( tran_type is service)
select account_no, id, date, pmt_amount, tran_type
from payment where service_id= <specific id>
order by date
Service table Data
ID Item_Typ Date Amt Acct#
1 SVC 11/12/2015 10 1
2 SVC 11/20/2015 20 1
3 SVC 12/13/2015 40 1
4 SVC 4/1/2016 30 1
Payment table Data
ID Svc_ID Item_Typ Date Amt Acct#
1 1 PMT 11/15/2015 5 1
2 1 PMT 11/15/2015 5 1
3 2 PMT 11/25/2015 40 1
4 3 PMT 12/28/2015 35 1
5 2 PMT 12/30/2015 -15 1
7 NULL PMT 1/1/2016 12 2
8 NULL PMT 3/1/2016 35 3
Query 1 Result
ID Item_Typ Date Amt Acct#
1 SVC 11/12/2015 10 1
2 SVC 11/20/2015 20 1
3 SVC 12/13/2015 40 1
4 SVC 4/1/2016 30 1
7 PMT 1/1/2016 12 2
8 PMT 3/1/2016 35 3
Final result after fetching payments for all query result related services
tranTyp Date Amt Acct#
SVC 11/12/2015 10 1
PMT 11/15/2015 5 1
PMT 11/15/2015 5 1
SVC 11/20/2015 20 1
PMT 11/25/2015 40 1
PMT 12/30/2015 -15 1
SVC 12/13/2015 40 1
PMT 12/28/2015 35 1
drop table if exists service;
create table service (ID int, Item_Typ varchar(3), `Date` date, Amt int, Acct int);
insert into service values
(1, 'SVC', '2015-11-12', 10 , 1),
(2, 'SVC', '2015-11-20', 20 , 1),
(3, 'SVC', '2015-12-13', 40 , 1),
(4, 'SVC', '2016-01-04', 30 , 1),
(5, 'SVC', '2015-10-04', 50 , 1)
drop table if exists payment;
create table payment(ID INT, Svc_ID INT, Item_Typ VARCHAR(3), `Date` DATE, Amt INT, Acct INT);
INSERT INTO payment values
(1, 1 , 'PMT', '2015-11-15', 5 , 1),
(2, 1 , 'PMT', '2015-11-15', 5 , 1),
(3, 2 , 'PMT', '2015-11-25', 40 , 1),
(4, 3 , 'PMT', '2015-12-28', 35 , 1),
(5, 2 , 'PMT', '2015-12-30', -15 ,1),
(7, NULL , 'PMT', '2016-01-01', 12 , 2),
(8, NULL , 'PMT', '2016-03-01', 35 , 3);
MariaDB [sandbox]> select * from
-> (
-> select 1 as typ,id,Item_typ,`date`, `date` as svc_date,amt,acct from service
-> union all
-> select 2,p.svc_id,p.Item_typ,p.`date`,
-> case when s.id is null then now()
-> else s.`date`
-> end as svc_date,
-> p.amt, p.acct from payment p
-> left join service s on p.svc_id = s.id
-> ) s
->
-> order by s.svc_date,s.acct,s.typ,s.id
-> ;
Related
Eg. If we have a table like this exam_score (record_date refers to month when record is taken, Jan = 1, Feb = 2 etc):
student
country
score
record_date
1
US
70
1
2
US
60
2
3
US
80
3
4
IT
60
2
5
IT
100
4
6
BR
80
5
Which SQL query allows me to generate a table where, for each student, we obtain the highest score obtained by fellow students in the same country before him?
So in this case, I should have something like
student
country
score
record_date
max_score
1
US
70
1
null (no usa students before him)
2
US
60
2
70 (among students before him, top score is 70)
3
US
80
3
70 (among students before him, top score is 70)
4
IT
60
2
null (no italy students before him)
5
IT
100
4
60
6
BR
80
5
null
Currently my workaround is to use Python together with SQL queries to get what I want, but could we do this with SQL alone? I'm using MySQL, but maybe the database doesn't matter in terms of the SQL query.
You can have SELECT in the column:
select s.*,
(select max(score)
from students
where country = s.country
and record_date < s.record_date) as max_score
from students s
order by record_date
Schema (MySQL v8.0)
CREATE TABLE exam_score
(`student` int, `country` varchar(2), `score` int, `record_date` int)
;
INSERT INTO exam_score
(`student`, `country`, `score`, `record_date`)
VALUES
(1, 'US', 70, 1),
(2, 'US', 60, 2),
(3, 'US', 80, 3),
(4, 'IT', 60, 2),
(5, 'IT', 100, 4),
(6, 'BR', 80, 5)
;
Query #1
SELECT student
, country
, score
, record_date
, MAX(lag_score) OVER (PARTITION BY country ORDER BY record_date) AS max_score
FROM (
SELECT student
, country
, record_date
, score
, LAG(score) OVER (PARTITION BY country ORDER BY record_date) AS lag_score
FROM exam_score
) a
ORDER BY student;
Output:
student
country
score
record_date
max_score
1
US
70
1
2
US
60
2
70
3
US
80
3
70
4
IT
60
2
5
IT
100
4
60
6
BR
80
5
View on DB Fiddle
I am getting the sum of all the rows in registration table. Table structure looks like this:
row_id row_registration_fee row_amount row_class row_user_id
1 200 1000 18 1
2 200 2510 18 1
3 0 1600 19 2
4 0 1500 19 1
5 200 1254 19 3
6 200 3000 19 1
7 200 2000 19 1
8 0 100 20 1
9 0 300 20 2
A user can have multiple registration fee. And I need to get the sum of all the row_registration_fee by row_class. The result should be like this:
row_registration_fee row_class
200 18
400 19
0 20
My select :
SELECT (COUNT(DISTINCT(row_user_id))* 200) as 'fee'
FROM registration
WHERE row_registration_fee > 0
GROUP BY row_class
Is there a better query here that can give a result like the above sample?
The result will be displayed in table rows using foreach loop in PHP. As of now, it will only give me two results for the registration fee, the row_class 18 and row_class 19 it excludes the row_class 20 because it only selects the user with a fee.
Additional explanation: The user with 2 or more registration fees should count only as one if the user has a total of 400 fees it should sum only 200.
Another approach might be to find the most recent row_id. In this case I have altered the data so that the first entry appears to be an error (fee = 300) followed by the second entry for the correct amount .
drop table if exists t;
create table t(
row_id int,row_registration_fee int,row_amount int, row_class int, row_user_id int);
insert into t values
(1 , 300 , 1000 , 18 , 1),
(2 , 200 , 2510 , 18 , 1),
(3 , 0 , 1600 , 19 , 2),
(4 , 0 , 1500 , 19 , 1),
(5 , 200 , 1254 , 19 , 3),
(6 , 200 , 3000 , 19 , 1),
(7 , 200 , 2000 , 19 , 1),
(8 , 0 , 100 , 20 , 1),
(9 , 0 , 300 , 20 , 2)
;
select sum(row_registration_fee),row_class
from
(
select t.row_class,t.row_registration_fee
from t
where t.row_id = (select max(row_id) from t t1 where t1.row_user_id = t.row_user_id and t1.row_class = t.row_class)
) a
group by row_class;
+---------------------------+-----------+
| sum(row_registration_fee) | row_class |
+---------------------------+-----------+
| 200 | 18 |
| 400 | 19 |
| 0 | 20 |
+---------------------------+-----------+
3 rows in set (0.00 sec)
To get those expected results then it seems to me that you first need to get the unique records per (row_registration_fee, row_class, row_user_id) tupple.
You can use a sub-query with a DISTINCT for that.
Then sum the row_registration_fee.
SELECT
SUM(row_registration_fee) as fee,
row_class
FROM
(
SELECT DISTINCT row_class, row_user_id, row_registration_fee
FROM registration
) q
GROUP BY row_class
Or via a GROUP BY and get the MAX fee.
SELECT
SUM(max_fee) as fee,
row_class
FROM
(
SELECT
row_class, row_user_id,
MAX(row_registration_fee) as max_fee
FROM registration
GROUP BY
row_class, row_user_id
) q
GROUP BY row_class
But to fix your current query, you could remove that WHERE clause.
And then use a CASE WHEN to return NULL on a zero row_registration_fee.
Because a count by value doesn't count the NULL's.
SELECT
COUNT(DISTINCT CASE WHEN row_registration_fee = 0 THEN row_user_id END) * 200 as fee,
row_class
FROM registration
GROUP BY row_class
You need to group by two columns and take sum:
select row_class, sum(row_registration_fee)
from registration
group by row_class, row_user_id
SELECT SUM(row_registration_fee), row_class, row_user_id
FROM registration
WHERE row_registration_fee > 0
GROUP BY row_class, row_user_id;
Yeah, group by two columns you can get per class and user.
I need your help. I have a table (senosrId, time, data), and I need to select the latest data from each day for one of the sensors for the latest 10 days.
For MS SQL, tested, compiled:
Test table:
CREATE TABLE [dbo].[DataTable](
[SensorId] [int] NULL,
[SensorTime] [datetime] NULL,
[SensorData] [int] NULL
)
Run several times to insert demo data:
insert into DataTable (SensorId, SensorTime, SensorData) select 1, getdate() - 15*rand(), convert(int, rand()*100)
Get last value for each of the last 10 days (actual answer):
select top 10 *
from DataTable
inner join ( -- max time for each day
select SensorId, max(SensorTime) as maxtime, convert(varchar(10), SensorTime, 112) as notneededcolumn
from DataTable
group by SensorId, convert(varchar(10), SensorTime, 112)
) lastvalues on lastvalues.maxtime=DataTable.SensorTime and lastvalues.SensorId=DataTable.SensorId
where DataTable.SensorId=1
order by DataTable.SensorTime desc
Example output:
1 2017-05-17 21:07:14.840 54 1 2017-05-17 21:07:14.840 20170517
1 2017-05-16 23:35:37.220 94 1 2017-05-16 23:35:37.220 20170516
1 2017-05-14 22:35:48.970 8 1 2017-05-14 22:35:48.970 20170514
1 2017-05-13 14:56:34.557 94 1 2017-05-13 14:56:34.557 20170513
1 2017-05-12 22:28:55.400 89 1 2017-05-12 22:28:55.400 20170512
I am developing an SSRS report with the following dataset (Table-1). I am grouping by Account and Period. My goal is to get the Total Expense and the Budget within a group. Because the Budget data is duplicated per group, I cannot use SUM() function for Budget. How do I remove the duplicates so the new dataset looks like this? (Table-2) Please advice. Thank you for your time.
Table-1
ID Account Period Expense Budget
1 100 201301 20 100
2 100 201301 30 100
3 100 201302 10 150
4 100 201302 40 150
5 200 ...................
Table-2
ID Account Period Expense Budget
1 100 201301 20 100
2 100 201301 30 NULL
3 100 201302 10 150
4 100 201302 40 NULL
5 200 ...................
If you really want to make duplicate budgets null try this update command
please check sqlfiddle http://sqlfiddle.com/#!3/1e619/11
Update table1
set budget = null
where id in
(
select aa.id from
(
select id,row_number()
over(partition by Budget order by Period) as rno
from table1
) aa
where rno > 1
);
select * from table1;
good luck.
I would use a windowed function if you have to do that grouping in SQL. If you can do it in SSRS just add a 'Row Grouping Parent' it would be better.
For SQL I would do this:
declare #Temp table ( ID int identity, Account int, period int, expense int, budget int);
insert into #Temp values (100, 201301, 20, 100),(100, 201301, 30, 100),(100, 201302, 10, 150),(100, 201302, 40, 150)
select *
from #Temp
select
ID
, Account
, Period
, Expense
, case when (row_number() over(partition by Budget order by Period) = 1) then Budget end as Budget-- only shows first occurrence of an order amount ordering by person
from #Temp
Please review my tables below... Is it possible to build a single query capable of
1) calculating the SUM of total_time for all vehicles that have class_id 1 (regardless of feature_id)(result would be 6:35)
2) calculating the SUM of total_time for all vehicles that have class_id 1 AND have feature_id 2(result would be 5:35 based on vehicle_id 22 and 24)
I'm able to get the results in two seperate queries, but I was hoping to retrieve them in one single query.... something like:
SELECT
SUM((CASE WHEN (VEHICLE_TABLE.class_id = 1) then LOG_TABLE.total_time else 0 end)) **AS TOTAL_ALL**,
...here goes statement for 2)... AS TOTAL_DIESEL...
FROM LOG_TABLE, VEHICLE_TABLE .....
WHERE VEHICLE_TABLE.vehicle_id = LOG_TABLE.vehicle_id ......
TABLE 1: LOG_TABLE (vehicle_id is NOT unique)
vehicle_id | total_time
--------------|--------------
22 2:00
22 0:30
23 1:00
24 2:20
24 0:45
TABLE 2: VEHICLE_TABLE (vehicle_id is unique)
vehicle_id | class_id
--------------|--------------
22 1
23 3
24 1
TABLE 3: VEHICLE_FEATURES_TABLE (vehicle_id is NOT unique but feature_id is unique per vehicle_id)
vehicle_id | feature_id
--------------|--------------
22 1
22 2
23 1
23 2
23 6
24 2
24 6
SELECT SUM(lt.total_time) AS TOTAL_ALL,
SUM(CASE WHEN (vft.feature_id IS NOT NULL) then LOG_TABLE.total_time else 0 end) AS FEATURE_TOTAL
FROM VEHICLE_TABLE vt
JOIN LOG_TABLE lt
ON vt.vehicle_id = lt.vehicle_id
LEFT JOIN VEHICLE_FEATURES_TABLE vft
ON vt.vehicle_id = vft.vehicle_id AND vft.feature_id = 2
WHERE vt.class_id = 1
It seems that there is not much point in putting both of them in one query unless you want the results together.
If so, just add a UNION between the 2 queries.
If you want to have both values in the same row try something like this:
SELECT (SELECT Sum(X)
FROM TBL
WHERE CLASS_ID = 1) AS CLS_id1,
(SELECT Sum(X)
FROM TBL
WHERE CLASS_ID = 1
AND FEATURE_ID = 2) AS CLS_id1_FTR_ID2