How to tune UNION ALL query? - mysql

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

Related

Get the count and total count joining 2 tables in mysql

I have 2 tables in the MySQL database :
1.
p_code{
code varchar(10) primary key,
discount decimal(4,2) not null,
valid_till date not null,
daily int not null,
total int non null,
max_amount decimal (6, 2) not null
}
2.
p_user{
code varchar(10) not null,
email varchar(50) not null,
date date not null,
primary key (code, email, date),
foreign key (code) references p_code(code)
}
now I want to get for a code in p_code total how many times an email has been used, total how many time the email has been used today and the details of the code.
I have tried the following query :
SELECT pc.discount, pc.valid, pc.daily, pc.total, pc.max_amount, c.tcount, c.count
FROM p_code AS pc
LEFT JOIN (
SELECT t.code, t.email, t.tcount, p.count
FROM (
SELECT code, email, COUNT( email ) AS tcount
FROM p_user
GROUP BY code, email
) AS t
LEFT JOIN (
SELECT code, email, COUNT( email ) AS count
FROM p_user
WHERE `date` = CURDATE( )
GROUP BY code, email
) AS p ON ( t.code, t.email ) = ( p.code, p.email )
) AS c ON pc.code = c.code
WHERE c.email = ?
AND pc.code = ?
But the problem is that if I do not have any entry for the code and email in the table p_user, it does not return any row.
What I require that it should return all the columns from p_code and 0 and 0 for tcount and count columns.
I think you can simplifiy your query this way, and anyway you'll need to put the condition on the left joined data... in the left join.
SELECT
c.discount,
c.valid,
c.daily,
c.total,
c.max_amount,
count(u.email) as totalCount,
sum(case when u.`date` = CURDATE() then 1 else 0 end) as dailyCount
FROM p_code c
LEFT JOIN p_user u on u.code = c.code and u.email = ?
WHERE c.code = ?
GROUP BY c.discount, c.valid, c.daily, c.total, c.max_amount
You could also do, for the "filter" on email :
WHERE c.code = ? and (u.email is null or u.email = ?)
You need to use the IFNULL function.
IFNULL(expr1,expr2)
If expr1 is not NULL, IFNULL() returns expr1; otherwise it returns
expr2.
You need to modify your query like:
SELECT pc.discount
, pc.valid
, pc.daily
, pc.total
, pc.max
, IFNULL(c.tcount, 0) AS tcount
, IFNULL(c.count, 0) as count
FROM p_code AS pc
...

fetch datas from two tables and differentiate between them

I have two tables and want displays rows from the two one in the same page ordered by date created.
Here my query:
SELECT R.*, R.id as id_return
FROM return R
UNION
ALL
SELECT A.*, A.id as id_buy
FROM buy A
WHERE
R.id_buyer = '$user' AND R.id_buyer = A.id_buyer AND (R.stats='1' OR R.stats='3') OR A.stats='4'
ORDER
BY R.date, A.date DESC LIMIT $from , 20
With this query i get this error message:
Warning: mysqli_fetch_array() expects parameter 1 to be mysqli_result, boolean given in ...
And here how i think i can differentiate between the results: (Knowing if the result is from the table RETURN or from the table BUY)
if(isset($hist_rows["id_return"])) {
// show RETURN rows
} else {
// show BUY rows
}
Please what is wrong with the query, and if the method to differentiate between tables are correct ?
EDIT
Here my tables sample:
CREATE TABLE IF NOT EXISTS `return` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`id_buyer` INT(12) NOT NULL,
`id_seller` INT(12) NOT NULL,
`message` TEXT NOT NULL,
`stats` INT(1) NOT NULL,
`date` varchar(30) NOT NULL,
`update` varchar(30)
PRIMARY KEY (`id`)
)
CREATE TABLE IF NOT EXISTS `buy` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`id_buyer` INT(12) NOT NULL,
`product` INT(12) NOT NULL,
`title` VARCHAR(250) NOT NULL,
`stats` INT(1) NOT NULL,
`date` varchar(30) NOT NULL
PRIMARY KEY (`id`)
)
Be sure the two table return and buy have the same number (and type sequence) of colummns .. if not the query fails
try select only the column you need from both the table and be sure that these are in correspondenting number and type
SELECT R.col1, R.col2, R.id as id_return
FROM return R
UNION ALL
SELECT A.col1, A.col2, A.id as id_buy
FROM buy A
WHERE
........
Looking to your code you should select the same number and type of column form boith the table eg de sample below:
(where i have added the different column and selecting null from the table where are not present)
I have aslore referred the proper where condition to each table ..
SELECT
R.'from return' as `source_table`
, R.`id`
, R.`id_buyer`
, null as product
, null as title
, R.`id_seller` as id_seller
, R-`message`
, R.`stats`
, R.`date`
, R.`update`
FROM return R
WHERE R.id_buyer = '$user'
AND (R.stats='1' OR R.stats='3')
UNION ALL
SELECT
A.'from buy'
, A.`id`
, A.`id_buyer`
, A.`product`
, A.`title`
, null
, null
, A.`stats`
, A.`date`
, null
FROM buy A
WHERE
A.id_buyer = '$user'
AND A.stats='4'
ORDER BY `source table`, date DESC LIMIT $from , 20
for retrive te value of the first column you should use in your case
echo $hist_rows["source_table"];
Otherwise i the two table are in some way related you should look at a join (left join) for link the two table and select the the repated column
(but this is another question)
But if you need left join you can try
SELECT
R.`id`
, R.`id_buyer`
, R.`id_seller` as id_seller
, R-`message`
, R.`stats`
, R.`date`
, R.`update`
, A.`id`
, A.`id_buyer`
, A.`product`
, A.`title`
, null
, null
, A.`stats`
, A.`date`
FROM return R
LEFT JOIN buy A ON R.id_buyer = A.id_buyer
AND R.id_buyer = '$user'
AND (R.stats='1' OR R.stats='3')
AND A.stats='4'
ORDER BY R.date DESC LIMIT $from , 20
When you use union all, the queries need to have exactly the same columns in the same order. If the types are not quite the same, then they are converted to the same type.
So, you don't want union all. I'm guessing you want a join. Something like this:
SELECT r.co1, r.col2, . . ., r.id as id_return,
b.col1, b.col2, . . ., b.id as id_buy
FROM return r JOIN
buy b
ON r.id_buyer = b.id_buyer
WHERE r.id_buyer = '$user' and
(r.stats in (1, 3) OR A.stats = 4)
ORDER BY R.date, A.date DESC
LIMIT $from, 20;
This query is only a guess as to what you might want.
Since you're using a union, select a string that you set identifying each query:
SELECT 'R', R.*, R.id as id_return
FROM return R
UNION
ALL
SELECT 'A', A.*, A.id as id_buy
This way your string 'R' or 'A' is the first column, showing you where it came from. We can't really know why it's failing without the full query, but I'd guess your $from might be empty?
As for your
Warning: mysqli_fetch_array() expects parameter 1 to be mysqli_result, boolean given in ...
Run the query directly first to get the sql sorted out before putting it into your PHP script. The boolean false indicates the query failed.

There is already an object named 'PDBCompr' in the database

I am using SQL server 2008. I want to insert data from a temp table to database. I am using While loop to insert data from temp table to database table.
Now I am facing an issue:
object already exist in the database.
declare #rev as int ,
#sQuotationNo NVARCHAR(15),
#sQRevNo int
set #rev=(select top 1 QRevNo from PDBCompr Where QuotationNo='JCS_G1415_008' and QRevNo<>'3' order by QRevNo desc)
;with cte as
(
SELECT
ROW_NUMBER() OVER(ORDER BY QuotationNo) AS sSLNO,
[CompanyCode] ,
[ProjectCode] ,
[PRevNo],
[CSlNo],
[ComprDescription] ,
[PID] ,
[RatingCode] ,
[Rating] ,
[StdSystems] ,
[BoosterSystems] ,
[GCUSystems] ,
[KOFSystems] ,
[HeaterSystems] ,
[OtherSystems] ,
[Comments] ,
[Currency1] ,
[UnitPrice1] ,
[Currency2] ,
[ExchRate2] ,
[UnitPrice2] ,
[Currency3],
[ExchRate3] ,
[UnitPrice3] ,
[CreateId] ,
[CreateDate] ,
[UpdateId] ,
[UpdateDate]
from PDBCompr
where QuotationNo='JCS_G1415_008' and CompanyCode ='001' and QRevNo ='2'
and AddCmprId not in (select distinct AddCmprId from PDBCompr where QuotationNo='JCS_G1415_008' and CompanyCode ='001' and QRevNo ='3' )
)
select * into #temp from cte
declare #cnt int , #loopCnt int=1
select #cnt =( select COUNT(*) from #temp)
while (#loopCnt<=#cnt)
begin
;with cte2 as
(
SELECT
sSLNO,
[CompanyCode] ,
[ProjectCode] ,
(select Max(PRevNo)+1 from PDBCompr) [PRevNo],
(select Max(CSlNo) +1 from PDBCompr)[CSlNo],
[ComprDescription] ,
[PID] ,
[RatingCode] ,
[Rating] ,
[StdSystems] ,
[BoosterSystems] ,
[GCUSystems] ,
[KOFSystems] ,
[HeaterSystems] ,
[OtherSystems] ,
[Comments] ,
[Currency1] ,
[UnitPrice1] ,
[Currency2] ,
[ExchRate2] ,
[UnitPrice2] ,
[Currency3],
[ExchRate3] ,
[UnitPrice3] ,
[CreateId] ,
[CreateDate] ,
[UpdateId] ,
[UpdateDate]
from #temp where sSLNO=#loopCnt
)
select * into PDBCompr from cte2
set #loopCnt= #loopCnt+1
drop table #temp
end
Please help me to find a proper solution. Thanks in advance
select * into PDBCompr from cte2
in this line you are again making a table "PDBCompr", but in above code you are selecting records from this table it means its already exist.
so if you want to insert record in this table then you need to change your query like below:
insert into PDBCompr select * from cte2

Mysql double join SUM invoice total and payments

I build an invoicing system, now i try to get an view with each invoice with the total price and the amount to pay.
I left joined the invoice_part table on the invoice and summed it up, now i want to also join the payments table but when i sum it sometimes takes the payment more than once. Is there a way i can accomplish the join to only take a payment once?
$stmt->from(array('x'=>'invoice'),array('x.id as id','x.ref_id as ref_id','x.start_date as start_date','x.regard as regard','x.project_code as project_code'));
$stmt->joinLeft(array('ip'=>'invoice_part'),'x.id=ip.invoice_id','SUM(ip.price*ip.amount*(100-ip.discount)/100) as price_ex');
$stmt->joinLeft(array('p'=>'payment'),'x.id=p.invoice_id','SUM(ip.price*ip.amount*(100-ip.discount)*(100+tax)/10000)-IFNULL(SUM(p.amount),0) as price_open');
//joins the payment multiple times if there are multiple invoice parts, payment should only be joined once
//note: there can be multiple payments for one invoice
$stmt->group('x.id');
result query:
SELECT `x`.`id` , `x`.`ref_id` , `x`.`start_date` , `x`.`regard` , `x`.`project_code` , `o`.`name` AS `contact` , `d`.`name` AS `department` , `c`.`name` AS `company` , `is`.`name` AS `status` , SUM( ip.price * ip.amount * ( 100 - ip.discount ) /100 ) AS `price_ex` , SUM( ip.price * ip.amount * ( 100 - ip.discount ) * ( 100 + tax ) /10000 ) - IFNULL( SUM( p.amount ) , 0 ) AS `price_open`
FROM `invoice` AS `x`
LEFT JOIN `invoice_part` AS `ip` ON x.id = ip.invoice_id
LEFT JOIN `payment` AS `p` ON x.id = p.invoice_id
GROUP BY `x`.`id`
so when i have 2 invoice parts and 1 payment for an invoice. the payment gets counted twice. how can i only let it be counted once?
You can divide the sum by the number of repetitions like this:
SUM(tbl.field_to_sum) / COUNT(tbl.primary_key) * COUNT(DISTINCT tbl.primary_key)
Also check out this question

sub query in sql server

I have two tables :
create table sales (
unitcode int ,
categorycode smallint ,
ddate varchar(10) ,
price float
)
and
create table timetable (
year varchar(4) ,
month varchar(11) ,
ddate varchar(10)
)
I want to write a subquery to find :
in each month in each year which 2 products(unitcode , categorycode) have a top of price ?
Try this
;WITH cte AS (
SELECT unitcode,categorycode,t.ddate,price,ROW_NUMBER() OVER (PARTITION BY t.[year],t.[month] ORDER BY price desc) AS price_order,t.[year],t.[month]
FROM sales s
INNER JOIN timetable t
ON t.ddate = s.ddate
)
SELECT *
FROM cte
WHERE price_order <= 2
ORDER BY [year] ASC,[month] ASC,price DESC