Related
I have tables that hold payroll header & detail info on a separate table. Schema of each below,
CREATE TABLE `Payroll` (
`payId` int,
`groupId` int,
`startDate` date ,
`endDate` date,
`paymentDate` date
);
insert into Payroll values
(20,2,'2022-06-01','2022-06-30','2022-06-30'),
(21,2,'2022-07-01','2022-07-31','2022-07-31'),
(18,1,'2022-05-01','2022-05-31','2022-05-31'),
(19,1,'2022-07-01','2022-07-31','2022-07-31')
;
CREATE TABLE `PayrollItems` (
`payId` int NOT NULL,
`employeeId` int ,
`payCategory` varchar(45) ,
`value` decimal(15,4) NOT NULL
);
insert into PayrollItems values
(20,12,'salary',200),
(20,12,'housing',500),
(20,13,'salary',400),
(20,14,'salary',1300),
(21,12,'salary',200),
(21,12,'housing',500),
(21,13,'salary',400),
(21,14,'salary',1300),
(18,13,'salary',400),
(18,13,'housing',1300),
(19,14,'salary',500),
(19,14,'housing',1200)
;
I am trying to get a query wherein given a payid i should get the previous payid details. Previous payid is identified by a combination of the paymentDate & groupId fields.
Therefore, for the data above, for payid 19 i should get records of payid 18 i.e each pay item value, as they both are of the same groupid, 1 , and paymentDate of payid 18 is prior to paymentDate of payid 19. There could be more records that have a paymentDate dated prior to payid 19, but only first record dated prior is required.
I tried,
SELECT
y.*
FROM
Payroll ppi2
JOIN
PayrollItems prr3 ON (`prr3`.`payId` = `ppi2`.`payId`)
LEFT JOIN
(SELECT
prr3.employeeId,
ppi2.paymentDate,
ppi2.payId,
ppi2.groupId,
'Last months standard salary' AS Particulars,
MAX(CASE
WHEN TRIM(prr3.payCategory) = 'salary' THEN value
ELSE 0
END) salary,
MAX(CASE
WHEN TRIM(prr3.payCategory) = 'housing' THEN value
ELSE 0
END) housing
FROM
Payroll ppi2
JOIN PayrollItems prr3 ON (`prr3`.`payId` = `ppi2`.`payId`)
AND ppi2.payId = 19
GROUP BY ppi2.payId , prr3.employeeId , ppi2.paymentDate,ppi2.groupId
ORDER BY ppi2.paymentDate DESC) AS y ON (y.groupId = ppi2.groupId)
AND y.paymentDate < ppi2.paymentDate
GROUP BY y.payId,y.employeeId,y.paymentDate,y.groupId,y.Particulars;
but i am not getting any results.
Expected result,given payid = 19, would be,
payid employeeid housing salary
18 13 1300 400
Would there be another way of doing this ?
dbfiddle
WITH
cte AS (
SELECT t1.payid prev_payid
FROM Payroll t1
JOIN Payroll t2 USING (groupId)
WHERE t2.payid = #payid
AND t1.startDate < t2.startDate
ORDER BY t1.startDate DESC LIMIT 1
)
SELECT payId,
employeeId,
SUM(value * (payCategory = 'housing')) housing,
SUM(value * (payCategory = 'salary')) salary
FROM PayrollItems
CROSS JOIN cte
WHERE payid = prev_payid
GROUP BY 1, 2
https://dbfiddle.uk/1LAdyksH
I have a table like the one above, I want to calculate total of amount with largest log_id and group by user_id.
the results will be as below :
this is my example code but this is not work :(
CREATE TABLE IF NOT EXISTS example (
`id` INT,
`log_id` INT,
`user_id` INT,
`amount` INT
);
INSERT INTO example VALUES
(1,1,10,4),
(2,2,10,8),
(3,3,10,2),
(4,3,10,6),
(5,1,12,9),
(6,2,12,4),
(7,1,13,7),
(8,1,14,2),
(9,2,14,6),
(10,1,15,7),
(11,2,15,4),
(12,3,15,9),
(13,3,15,6);
select max(log_id) as log_id, user_id, amount from example group by user_id, amount
Any ideas?
Thanks, xmush
You can go for RANK function and get data.
Schema (MySQL v8.0)
CREATE TABLE test
(
id INT,
logid int,
userid int,
amount INT
);
insert into test(id, logid, userid, amount)
values (1, 1, 12,9),
(2,2,12,4);
Query #1
WITH testcte AS (
SELECT *, RANK() OVER (PARTITION BY userid order by logid desc) as rnk from test
)
SELECT * FROM testcte WHERE rnk = 1;
id
logid
userid
amount
rnk
2
2
12
4
1
View on DB Fiddle
Here's a 'traditional' (pre-8.0) approach...
Grab the rows holding the largest log_id for each user...
SELECT x.*
FROM example x
JOIN
( SELECT user_id
, MAX(log_id) log_id
FROM example
GROUP
BY user_id
) y
ON y.user_id = x.user_id
AND y.log_id = x.log_id
Aggregate the resulting data set...
SELECT x.log_id
, x.user_id
, SUM(amount) total
FROM example x
JOIN
( SELECT user_id, MAX(log_id) log_id FROM example GROUP BY user_id ) y
ON y.user_id = x.user_id
AND y.log_id = x.log_id
GROUP
BY x.log_id
, x.user_id
ORDER
BY user_id;
I have came up with the following schema:
CREATE TABLE products
(
id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL,
name VARCHAR(255) NOT NULL,
quantity INT(10) UNSIGNED NOT NULL,
purchase_price DECIMAL(8,2) NOT NULL,
sell_price DECIMAL(8,2) NOT NULL,
provider VARCHAR(255) NULL,
created_at TIMESTAMP NULL,
PRIMARY KEY (id)
);
# payment methods = {
# "0": "CASH",
# "1": "CREDIT CARD",
# ...
# }
CREATE TABLE orders
(
id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL,
product_id INT(10) UNSIGNED NOT NULL,
quantity INT(10) UNSIGNED NOT NULL,
payment_method INT(11) NOT NULL DEFAULT 0,
created_at TIMESTAMP NULL,
PRIMARY KEY (id),
FOREIGN KEY (product_id) REFERENCES products(id)
);
# status = {
# "0": "PENDING"
# "1": "PAID"
# }
CREATE TABLE invoices
(
id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL,
price INT(10) UNSIGNED NOT NULL,
status INT(10) UNSIGNED NOT NULL DEFAULT 0,
created_at TIMESTAMP NULL,
PRIMARY KEY (id)
);
# payment methods = {
# "0": 'CASH',
# "1": 'CREDIT CARD',
# ...
# }
CREATE TABLE bills
(
id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL,
name VARCHAR(255) NOT NULL,
payment_method INT(10) UNSIGNED NOT NULL DEFAULT 0,
price DECIMAL(8,2) NOT NULL,
created_at TIMESTAMP NULL,
PRIMARY KEY (id)
);
And the following query to select a balance:
SELECT ((orders + invoices) - bills) as balance
FROM
(
SELECT SUM(p.sell_price * o.quantity) as orders
FROM orders o
JOIN products p
ON o.product_id = p.id
) orders,
(
SELECT SUM(price) as invoices
FROM invoices
WHERE status = 1
) invoices,
(
SELECT SUM(price) as bills
FROM bills
) bills;
Its working and returning the right balance, but I want to create a chart using Morris.js and I need to change it to return a daily or monthly balance at a given period of time and in this format:
Daily (2017-02-27 to 2017-03-01)
balance | created_at
--------------------------
600.00 | 2017-03-01
50.00 | 2017-02-28
450.00 | 2017-02-27
And monthly (2017-01 to 2017-03)
balance | created_at
--------------------------
200.00 | 2017-03
250.00 | 2017-02
350.00 | 2017-01
What I need to change in my schema or query to return results in this way?
http://sqlfiddle.com/#!9/2289a9/2
Any hints are welcomed. Thanks in advance
Include the created_at date in the SELECT list and a GROUP BY clause in each query.
Ditch the old school comma operator for the join operation, and replace it with a LEFT JOIN.
To return dates for which there are no orders (or no payments, or no invoices) we need a separate row source that is guaranteed to return the date values. As an example, we could use an inline view:
SELECT d.created_dt
FROM ( SELECT '2017-02-27' + INTERVAL 0 DAY AS created_dt
UNION ALL SELECT '2017-02-28'
UNION ALL SELECT '2017-03-01'
) d
ORDER BY d.created_dt
The inline view is just an option. If we had a calendar table that contains rows for the three dates we're interested in, we could make use of that instead. What's important is that we have a query that is guaranteed to return to us exactly three rows with the distinct created_at date values we want to return.
Once we have that, we can add a LEFT JOIN to get the value of "bills" for that date.
SELECT d.created_dt
, b.bills
FROM ( SELECT '2017-02-27' + INTERVAL 0 DAY AS created_dt
UNION ALL SELECT '2017-02-28'
UNION ALL SELECT '2017-03-01'
) d
LEFT
JOIN ( SELECT DATE(bills.created_at) AS created_dt
, SUM(bills.price) AS bills
FROM bills
WHERE bills.created_at >= '2017-02-27'
AND bills.created_at < '2017-03-01' + INTERVAL 1 DAY
GROUP BY DATE(bills.created_at)
) b
ON b.created_dt = d.created_dt
ORDER BY d.created_dt
Extending that to add another LEFT JOIN, to get invoices
SELECT d.created_dt
, i.invoices
, b.bills
FROM ( SELECT '2017-02-27' + INTERVAL 0 DAY AS created_dt
UNION ALL SELECT '2017-02-28'
UNION ALL SELECT '2017-03-01'
) d
LEFT
JOIN ( SELECT DATE(bills.created_at) AS created_dt
, SUM(bills.price) AS bills
FROM bills
WHERE bills.created_at >= '2017-02-27'
AND bills.created_at < '2017-03-01' + INTERVAL 1 DAY
GROUP BY DATE(bills.created_at)
) b
ON b.created_dt = d.created_dt
LEFT
JOIN ( SELECT DATE(invoices.created_at) AS created_dt
, SUM(invoices.price) AS invoices
FROM invoices
WHERE invoices.status = 1
AND invoices.created_at >= '2017-02-27'
AND invoices.created_at < '2017-03-01' + INTERVAL 1 DAY
GROUP BY DATE(invoices.created_at)
) i
ON i.created_dt = d.created_dt
ORDER BY d.created_dt
Similarly, we can a LEFT JOIN to another inline view that returns total orders grouped by DATE(created_at).
It's important that the inline views return distinct value of created_dt, a single row for each date value.
Note that for dev, test and debugging, we can independently execute just the inline view queries.
When a matching row is not returned from a LEFT JOIN, for example no matching row returned from i because there were no invoices on that date, the query is going to return a NULL for the expression i.invoices. To replace the NULL with a zero, we can use the IFNULL function, or the more ANSI standard COALESCE function. For example:
SELECT d.created_dt
, IFNULL(i.invoices,0) AS invoices
, COALESCE(b.bills,0) AS bills
FROM ...
To get the results monthly, we'd need a calendar query that returns one row per month. Let's assume we're going to return a DATE value which as the first day of the month. For example:
SELECT d.created_month
FROM ( SELECT '2017-02-01' + INTERVAL 0 DAY AS created_month
UNION ALL SELECT '2017-03-01'
) d
ORDER BY d.created_month
The inline view queries will need to GROUP BY created_month, so they return a single value for each month value. My preference would be to use a DATE_FORMAT function to return the first day of the month, derived from created_at. But there are other ways to do it. The goal is return a single row for '2017-02-01' and a single row for '2017-03-01'. Note that the date ranges on created_at extend from '2017-02-01' up to (but not including) '2017-04-01', so we get the total for the whole month.
( SELECT DATE_FORMAT(bills.created_at,'%Y-%m-01') AS created_month
, SUM(bills.price) AS bills
FROM bills
WHERE bills.created_at >= '2017-02-01'
AND bills.created_at < '2017-03-01' + INTERVAL 1 MONTH
GROUP BY DATE_FORMAT(bills.created_at,'%Y-%m-01')
) b
I have a table, in which there are date wise quiz score of different users. I want to load top 5 scorers for every date.
Table sample create statement:
CREATE TABLE `subscriber_score` (
`msisdn` varchar(25) COLLATE utf8_unicode_ci NOT NULL,
`date` date NOT NULL,
`score` int(11) NOT NULL DEFAULT '0',
`total_questions_sent` int(11) NOT NULL DEFAULT '0',
`total_correct_answers` int(11) NOT NULL DEFAULT '0',
`total_wrong_answers` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`msisdn`,`date`),
KEY `fk_subscriber_score_subscriber1` (`msisdn`),
CONSTRAINT `fk_subscriber_score_subscriber1` FOREIGN KEY (`msisdn`) REFERENCES `subscriber` (`msisdn`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Query which I have tried:
SELECT subscriber.msisdn AS msisdn,subscriber.name AS name,subscriber.gender AS gender,tmp2.score AS score,tmp2.date AS winning_date
FROM subscriber,
(SELECT msisdn,tmp.date,tmp.score
FROM subscriber_score,
(SELECT date,MAX(score) AS score
FROM subscriber_score
WHERE date > '2014-10-10' AND date < '2014-11-10' GROUP BY date)
tmp
WHERE subscriber_score.date=tmp.date AND subscriber_score.score=tmp.score)
tmp2
WHERE subscriber.msisdn=tmp2.msisdn ORDER BY winning_date
Actual output: Only one top scorer for every date is shown.
Wanted Output Top 5(or say 10) records for every date are required.
I think you can do this using variables to assign each row a row number, then filter the top 5 for each date.
SELECT s.name AS name,
s.gender AS gender,
s.msisdn,
ss.date,
ss.score
FROM ( SELECT ss.msisdn,
ss.score,
#r:= CASE WHEN ss.Date = #d THEN #r + 1 ELSE 1 END AS RowNum,
#d:= ss.date AS winning_date
FROM subscriber_score AS ss
CROSS JOIN (SELECT #d:= '', #r:= 0) AS v
WHERE ss.date > '2014-10-10'
AND ss.date < '2014-11-10'
ORDER BY ss.Date, ss.Score DESC
) AS ss
INNER JOIN Subscriber AS s
ON s.msisdn = ss.msisdn
WHERE ss.RowNum <= 5;
Example on SQL Fiddle
refer this query its not complete but hope it helps
SELECT SCORE
FROM table
WHERE date='somedate'
ORDER BY SCORE DESC LIMIT 5
select bc.msisdn msisdn,bc.name name,bc.gender gender,ab.score score,ab.date winning_date
(
select msisdn,date,score,
dense_rank() over (partition by date order by score desc) rnk
from subscriber_score
) ab,subscriber bc
where bc.msisdn=ab.msisdn and ab.rnk<=5
order by winning_date ;
This is how you can get solution of your problem in oracle sql.
try below
SELECT subscriber.msisdn AS msisdn,subscriber.name AS name,subscriber.gender AS gender,tmp2.score AS score,tmp2.date AS winning_date
FROM subscriber inner join
(select msisdn,date, score, ROW_NUMBER() OVER(PARTITION BY date ORDER BY score DESC) AS Row
FROM subscriber_score
WHERE date > '2014-10-10' AND date < '2014-11-10' GROUP BY date)
tmp
on subscriber.msisdn=tmp.msisdn and tmp.row<=5
The following MySQL query results sum of credit and debit for each account code (acctcode) and produces over all total using union all. The table ledg_post has 5.7 million records and is indexed. Still the query takes 1 minute to execute. Please help me to tune this query.
select b.acnt_code as acctcode
, b.disp_name as acctname
, sum(amt_dr) as debit
, sum(amt_cr) as credit
, (sum(amt_dr)- sum(amt_cr)) as closingbalance
, a.txn_code as txn_code
from ledg_post a
, gl_acnts b
, mst_loan lmt
where a.acnt_code = b.acnt_code
group
by b.acnt_code
union all
select ' ' acctcode
, ' Grand Total ' acctname
, sum(amt_dr) debit
, sum(amt_cr) credit
, (sum(amt_dr)- sum(amt_cr)) closingbalance
, '' txn_code
from ledg_post a
, mst_loan lmt
where lmt.loan_id = a.ref_id
Table Definitions
create table ledg_post
( txn_code int(11)
, ref_id int(11)
, acnt_code int(11)
, amt_dr decimal(20, 2)
, amt_cr decimal(20, 2)
);
create table gl_acnts
( glm_acnt_code int
, glm_acnt_disp_name varchar(50)
);
create table mst_loan
( lmt_loan_id int(11)
, lmt_clnt_id int(11)
);
There is no need to use UNION ALL, You can achieve this using GROUP BY ... WITH ROLLUP
Try this:
SELECT b.acnt_code AS acctcode, IFNULL(b.disp_name, ' Grand Total ') AS acctname,
SUM(a.amt_dr) AS debit, SUM(a.amt_cr) AS credit,
(SUM(a.amt_dr)- SUM(a.amt_cr)) AS closingbalance,
IFNULL(a.txn_code, '') AS txn_code
FROM ledg_post a
INNER JOIN gl_acnts b ON a.acnt_code=b.acnt_code
GROUP BY acctcode WITH ROLLUP