Sum more than 1 table from SQL on codeigniter - php

need a small help to figure out this situation.
I see that codeigniter is not supporting UNION and i want to figure out this method to get the total sum for 2 tables, with same id of product.
The issue is that on sum 2 tables, the values are duplicating or multiplicative.
Link to SQL Fiddle
Structure :
create table products
(id int, name varchar(9));
insert into products
(id, name)
values
(1,'Product 1'),
(2,'Product 2'),
(3,'Product 3');
create table p_items
(puid int, prid int, quantity int);
insert into p_items
(puid, prid, quantity)
values
(11,1,100),
(11,2,100),
(11,2,100),
(14,2,100),
(14,3,100),
(15,3,100);
create table s_items
(puid int, prid int, quantity int);
insert into s_items
(puid, prid, quantity)
values
(11,1,1),
(11,2,1),
(11,2,1),
(13,2,1),
(15,3,1),
(13,3,1);
Execute in normal SQL:
select a.puid, b.name, a.prid, sum(a.quantity) as P_ITEMS, sum(c.quantity) as S_ITEMS
from p_items a
join products b
on b.id = a.prid
join s_items c
on c.prid = a.prid
group by a.prid;
Codeigniter Function:
$this->alerts
->select('products.id as productid, products.name, sumt(p_items.quantity), sum(s_items.quantity)', FALSE)
->from('products')
->join('p_items', 'products.id = p_items.prid', 'left')
->join('s_items', 'products.id = s_items.prid')
->group_by("products.id");
echo $this->alerts->generate();
Any help is very appreciated.

Your problem is your producing a cartesian product and thus getting your duplicated sums. Look at Product ID 3 for example. You're associating that with p_items prid = 3. By itself, that would return you 200. However, once you then join on s_items prid = 3, now for each row in s_items, it has to match with each row in p_items. Meaning:
14 3 100 15 3 1
14 3 100 15 3 1
15 3 100 15 3 1
15 3 100 15 3 1
---------------
400 4
Besides the product table, which table is your master table? If you use p_items, you want get row 13 from s_items. Likewise, if you use s_items, you won't get rows 14 from p_items.
You can accomplish this using a subquery:
select b.id,
b.name,
P_ITEMS,
S_ITEMS
from products b
left join
(SELECT prid, SUM(quantity) as P_Items
FROM p_items
GROUP BY prid)
a on b.id = a.prid
left join
(SELECT prid, SUM(quantity) as S_Items
FROM s_items
GROUP BY prid)
c on c.prid = a.prid
group by b.id, b.name
And the updated Fiddel: http://sqlfiddle.com/#!2/62b45/12
Good luck.

If the best way to get the answer you want is with a union query, and codeigniter does not let you write one, don't use codeigniter to write your query. Put it in a stored procedure and call the stored procedure with codeigniter.

Related

How to count every month records from Table1 and Store it in Table2

Let's Say I have 2 tables
In Table1 I have 3 columns named as
Table1
Id | Name | Date (Format: 12-01-2019)
1 ABC 22-08-2019
2 XYZ 23-07-2019
Now My Question is How to store the Month wise count in another Table(i.e, Table2)
Expected Result:
Table 2
Month | Count
08/Aug 1
07/July 1
I searched many queries but I didn't find a better one
Can anyone provide me this sql query?
OR
If can you provide SQL query which stores all these count in separate column in Table1 with an extra column
merge into table_2 tgt
using
(select trunc(dt, 'month') dt, count(*) cnt
from table_1
group by trunc(dt, 'month')
) src on (tgt.dt = src.dt)
when not matched then insert (tgt.dt, tgt.cnt)
values (src.dt, src.cnt);
INSERT INTO table2(month,count)
SELECT
MONTH(Date) AS m, COUNT(DISTINCT Id)
FROM
table1 GROUP BY m;
This can be done in one query
Insert into Table2 (Column1, Column2)
Select Count(*), DATEColumn from Table1 group by DATEColumn
I am not sure which database your are using this work on MSSQL.

Oracle PHP display only once

There are two tables:
$inner_query =
"SELECT A.*, ROWNUM AS RN, TO_CHAR(A.last_newsletter_modify, 'DD/MM/YYYY') AS LAST_NEWSLETTER_MODIFY2
FROM ".$db_schema_name."newsletter_subscription A,
".$db_schema_name."newsletter_type B,
".$db_schema_name."newsletter_subtyp_profile C
WHERE A.id_subscription = C.id_subscription AND
C.id_type = B.id_type
ORDER BY e_mail";
If I run this query for id_subscription 734 it displays 3 times.
How can to display it just once?
Short answer: you are getting one row per newsletter_subtyp_profile. Subscription 734 is linked to three newsletter types, hence three rows of output.
You have three tables, not two, and the question would be clearer if you included full descriptions and sample data, and also got rid of the irrelevant PHP aspect and focussed on the SQL.
With some detective work, I make it this:
create table newsletter_subscription
( id_subscription integer primary key
, last_newsletter_modify date
, e_mail varchar2(50) not null );
create table newsletter_type
( id_type integer primary key
, description varchar2(40) not null unique );
create table newsletter_subtyp_profile
( id_subscription references newsletter_subscription
, id_type references newsletter_type
, constraint nsp_pk primary key (id_type,id_subscription) );
insert into newsletter_subscription values (600, date '2017-01-10', 'someone#somewhere.net');
insert into newsletter_subscription values (734, date '2017-02-05', 'someone#somewhereelse.net');
insert into newsletter_subscription values (800, date '2017-03-01', 'nobody#nowherewhere.net');
insert into newsletter_type values (1, 'Type One');
insert into newsletter_type values (2, 'Type Two');
insert into newsletter_type values (3, 'Type Three');
insert into newsletter_subtyp_profile values (734, 1);
insert into newsletter_subtyp_profile values (734, 2);
insert into newsletter_subtyp_profile values (734, 3);
Now run your query (I shortened the select list to simplify the output, and added b.description - a dummy column as I don't know what other columns you have on newsletter_type):
select a.id_subscription, a.e_mail
, to_char(a.last_newsletter_modify, 'DD/MM/YYYY') as last_newsletter_modify2
, b.description
from newsletter_subscription a,
newsletter_type b,
newsletter_subtyp_profile c
where a.id_subscription = c.id_subscription and
c.id_type = b.id_type
order by a.e_mail, c.id_type;
ID_SUBSCRIPTION E_MAIL LAST_NEWSLETTER_MODIFY2 DESCRIPTION
--------------- -------------------------- ----------------------- ---------------------
734 someone#somewhereelse.net 05/02/2017 Type One
734 someone#somewhereelse.net 05/02/2017 Type Two
734 someone#somewhereelse.net 05/02/2017 Type Three
btw the logic would be clearer if you used mnemonic aliases such as sub instead of a for newsletter_subscription, and also used standard ANSI joins and lost the uppercase:
select sub.id_subscription, sub.e_mail
, to_char(sub.last_newsletter_modify, 'DD/MM/YYYY') as last_newsletter_modify
, typ.description
from newsletter_subscription sub
join newsletter_subtyp_profile pro on pro.id_subscription = sub.id_subscription
join newsletter_type typ on typ.id_type = pro.id_type
where sub.id_subscription = 734
order by sub.e_mail, pro.id_type;
You can use window function row_number to get one row per id_subscription with max (or min id_type if you change the order by clause in the function to order by id_type):
$inner_query = "SELECT *
FROM (
SELECT A.*,
ROWNUM AS RN,
TO_CHAR(A.last_newsletter_modify, 'DD/MM/YYYY') AS LAST_NEWSLETTER_MODIFY2,
row_number() over (
partition by C.id_subscription
order by C.id_type desc nulls last
) as seqnum
FROM ".$db_schema_name."newsletter_subscription A
JOIN ".$db_schema_name."newsletter_subtyp_profile C
on A.id_subscription = C.id_subscription
JOIN ".$db_schema_name."newsletter_type B
on C.id_type = B.id_type
) t
WHERE seqnum = 1
ORDER BY e_mail";
Also notice that I have replace the old comma based join with widely recommended explicit JOIN syntax.

MySql - if there is no record, insert multiple rows

Before asking this question, I already search a lot of entries on Google and StockOverflow. Nothing can fulfil my question.
There are two tables - group_sale_bonuses and members. I want to check is already there records with product_id "1" in the group_sale_bonuses.
If not, I want to insert all records from members table into group_sale_bonuses with product_id "1".
My overall requirement is as follow:
IF ((Select count(id) from group_sale_bonuses where product_id = 1) = 0) THEN
INSERT INTO group_sale_bonuses (member_id, product_id, quantity_counter, credit)
SELECT id, 1, 0, 0 FROM members
END IF
But this sql causes the errors.
I know there are solutions about Insert Ignore, Where Not Exists.
But these conditions checking are based on per each record. I have thousands of records in members table. I want to make condition checking just one time like in my above sql example.
By the way, I will use this Sql in Php web application.
You could just set the code in a WHERE clause instead of the IF.
INSERT INTO group_sale_bonuses(
member_id,
product_id,
quantity_counter,
credit)
SELECT
id, 1, 0, 0 FROM members
WHERE(
SELECT
count(id) FROM group_sale_bonuses
WHERE product_id = 1
) = 0;
This should do it for all product_id's
SELECT m.product_id, m.member_id FROM members AS m
LEFT JOIN group_sale_bonuses AS gsb ON gsb.product_id = m.product_id
WHERE gsb.product_id IS NULL ;
You can filter it to a specific product_id by adding to the where clause
SELECT m.product_id, m.member_id FROM members AS m
LEFT JOIN group_sale_bonuses AS gsb ON gsb.product_id = m.product_id
WHERE gsb.product_id IS NULL AND m.product_id = 1;
Take a look at this SQLfiddle: http://sqlfiddle.com/#!2/8482c/2

How to get 2 columns from one table and 2 rows as columns from other table in one row, in MySQL?

I know this is quite complicated, but I sincerely hope someone will check this out.
I made short version (to better understand the problem) and full version (with original SQL)
Short version:
[TABLE A] [TABLE B]
|1|a|b| |1|x
|2|c|d| |1|y
|3| | | |2|z
|5| | | |2|v
|4|w
How can I make MySQL query to get rows like that:
1|a|b|x|y
2|c|d|z|v
2 columns from A and 2 rows from B as columns, only with keys 1 and 2, no empty results
Subquery?
Full version:
I tried to get from Prestashop db in one row:
product id
ean13 code
upc code
feature with id 24
feature with id 25
It's easy to get id_product, ean13 and upc, as it's one row in ps_product table. To get features I used subqueries (JOIN didn't work out).
So, I selected id_product, ean13, upc, (subquery1) as code1, (subquery2) as code2.
Then I needed to throw out empty rows. But couldn't just put code1 or code2 in WHERE.
To make it work I had to put everything in subquery.
This code WORKS, but it is terribly ugly and I bet this should be done differently.
How can I make it BETTER?
SELECT * FROM(
SELECT
p.id_product as idp, p.ean13 as ean13, p.upc as upc, (
SELECT
fvl.value
FROM
`ps_feature_product` fp
LEFT JOIN
`ps_feature_value_lang` fvl ON (fp.id_feature_value = fvl.id_feature_value)
WHERE fp.id_feature = 24 AND fp.id_product = idp
) AS code1, (
SELECT
fvl.value
FROM
`ps_feature_product` fp
LEFT JOIN
`ps_feature_value_lang` fvl ON (fp.id_feature_value = fvl.id_feature_value)
WHERE fp.id_feature = 25 AND fp.id_product = idp
) AS code2,
m.name
FROM
`ps_product` p
LEFT JOIN
`ps_manufacturer` m ON (p.id_manufacturer = m.id_manufacturer)
) mainq
WHERE
ean13 != '' OR upc != '' OR code1 IS NOT NULL OR code2 IS NOT NULL
create table tablea
( id int,
col1 varchar(1),
col2 varchar(1));
create table tableb
( id int,
feature int,
cola varchar(1));
insert into tablea (id, col1, col2)
select 1,'a','b' union
select 2,'c','d' union
select 3,null,null union
select 5,null,null;
insert into tableb (id, feature, cola)
select 1,24,'x' union
select 1,25,'y' union
select 2,24,'z' union
select 2,25,'v' union
select 4,24,'w';
select a.id, a.col1, a.col2, b1.cola b1a, b2.cola b2a
from tablea a
inner join tableb b1 on (b1.id = a.id and b1.feature = 24)
inner join tableb b2 on (b2.id = a.id and b2.feature = 25);
SQLFiddle here.
What you want to do is called a Pivot Query. MySQL has no native support for pivot queries, though other RDBMSen do.
You can simulate a pivot query with derived columns, but you must specify each derived column. That is, it is impossible in MySQL itself to have the number of columns match rows of another table. This has to be known ahead of time.
It would be much easier to query the results as rows and then use PHP to do the aggregation into columns. For example:
while ($row = $result->fetch()) {
if (!isset($table[$row->id])) {
$table[$row->id] = array();
}
$table[$row->id][] = $row->feature;
This is not a simple question because it's not a standard query, by the way if you can make use of views you can do the following procedure. Assuming you're starting from this tables:
CREATE TABLE `A` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`firstA` char(1) NOT NULL DEFAULT '',
`secondA` char(1) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
);
CREATE TABLE `B` (
`id` int(11) unsigned NOT NULL,
`firstB` char(1) NOT NULL DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `A` (`id`, `firstA`, `secondA`)
VALUES (1, 'a', 'b'), (2, 'c', 'd');
INSERT INTO `B` (`id`, `firstB`)
VALUES (1, 'x'), (1, 'y'), (2, 'z'), (2, 'v'), (4, 'w');
First create a view that joins the two tables:
create or replace view C_join as
select A.firstA, A.secondA, B.firstB
from A
join B on B.id=A.id;
Create the view that groups the rows in table B:
create or replace view d_group_concat as
select firstA, secondA, group_concat(firstB) groupconcat
from c_join
group by firstA, secondA
Create the view that does what you need:
create or replace view e_result as
select firstA, secondA, SUBSTRING_INDEX(groupconcat,',',1) firstB, SUBSTRING_INDEX(SUBSTRING_INDEX(groupconcat,',',2),',',-1) secondB
from d_group_concat
And that's all. Hope this helps you.
If you can't create views, this could be the query:
select firstA, secondA, SUBSTRING_INDEX(groupconcat,',',1) firstB, SUBSTRING_INDEX(SUBSTRING_INDEX(groupconcat,',',2),',',-1) secondB
from (
select firstA, secondA, group_concat(firstB) groupconcat
from (
select A.firstA, A.secondA, B.firstB
from A
join B on B.id=A.id
) c_join
group by firstA, secondA
) d_group_concat
Big thanks to everyone for the answers. James's answer was first, simplest and works perfectly in my case. The query runs several times faster than mine, with subqueries. Thanks, James!
Just a few words why I needed that:
It's a part of integration component for Prestashop and wholesale exchange platform. There are 4 product code systems that wholesalers use on the platform (ean13, upc and 2 other systems). Those 2 other product codes are added as product feature in Prestashop. There are thousands of products on the shop and hundreds of thousands of products on the platform. Which is why speed is crucial.
Here is the code for full version of my question. Maybe someone will find this helpful.
Query to get Prestashop product codes and certain features in one row:
SELECT
p.id_product, p.ean13, p.upc, fvl1.value as code1, fvl2.value as code2
FROM `ps_product` p
LEFT JOIN
`ps_feature_product` fp1 ON (p.id_product = fp1.id_product and fp1.id_feature = 24)
LEFT JOIN
`ps_feature_value_lang` fvl1 ON (fvl1.id_feature_value = fp1.id_feature_value)
LEFT JOIN
`ps_feature_product` fp2 ON (p.id_product = fp2.id_product and fp2.id_feature = 25)
LEFT JOIN
`ps_feature_value_lang` fvl2 ON (fvl2.id_feature_value = fp2.id_feature_value)
WHERE
ean13 != '' OR upc != '' OR fvl1.value IS NOT NULL OR fvl2.value IS NOT NULL;

Need help in optimising query

I have two tables - incoming tours(id,name) and incoming_tours_cities(id_parrent, id_city)
id in first table is unique, and for each unique row from first table there is the list of id_city - s in second table(i.e. id_parrent in second table is equal to id from first table)
For example
incoming_tours
|--id--|------name-----|
|---1--|---first_tour--|
|---2--|--second_tour--|
|---3--|--thirth_tour--|
|---4--|--hourth_tour--|
incoming_tours_cities
|-id_parrent-|-id_city-|
|------1-----|---4-----|
|------1-----|---5-----|
|------1-----|---27----|
|------1-----|---74----|
|------2-----|---1-----|
|------2-----|---5-----|
........................
That means that first_tour has list of cities - ("4","5","27","74")
AND second_tour has list of cities - ("1","5")
Let's assume i have two values - 4 and 74:
Now, i need to get all rows from first table, where my both values are in the list of cities. i.e it must return only the first_tour (because 4 and 74 are in it's list of cities)
So, i wrote the following query
SELECT t.name
FROM `incoming_tours` t
JOIN `incoming_tours_cities` tc0 ON tc0.id_parrent = t.id
AND tc0.id_city = '4'
JOIN `incoming_tours_cities` tc1 ON tc1.id_parrent = t.id
AND tc1.id_city = '74'
And that works fine.
But i generate the query dynamically, and when the count of joins is big (about 15) the query slowing down.
i.e. when i try to run
SELECT t.name
FROM `incoming_tours` t
JOIN `incoming_tours_cities` tc0 ON tc0.id_parrent = t.id
AND tc0.id_city = '4'
JOIN `incoming_tours_cities` tc1 ON tc1.id_parrent = t.id
AND tc1.id_city = '74'
.........................................................
JOIN `incoming_tours_cities` tc15 ON tc15.id_parrent = t.id
AND tc15.id_city = 'some_value'
the query run's in 45s(despite on i set indexes in the tables)
What can i do, to optimaze it?
Thanks much
SELECT t.name
FROM incoming_tours t INNER JOIN
( SELECT id_parrent
FROM incoming_tours_cities
WHERE id IN (4, 74)
GROUP BY id_parrent
HAVING count(id_city) = 2) resultset
ON resultset.id_parrent = t.id
But you need to change number of total cities count.
SELECT name
FROM (
SELECT DISTINCT(incoming_tours.name) AS name,
COUNT(incoming_tours_cities.id_city) AS c
FROM incoming_tours
JOIN incoming_tours_cities
ON incoming_tours.id=incoming_tours_cities.id_parrent
WHERE incoming_tours_cities.id_city IN(4,74)
HAVING c=2
) t1;
You will have to change c=2 to whatever the count of id_city you are searching is, but since you generate the query dynamically, that shouldn't be a problem.
I'm pretty sure this works, but a lot less sure that it is optimal.
SELECT * FROM incoming_tours
WHERE
id IN (SELECT id_parrent FROM incoming_tours_cities WHERE id_city=4)
AND id IN (SELECT id_parrent FROM incoming_tours_cities WHERE id_city=74)
...
AND id IN (SELECT id_parrent FROM incoming_tours_cities WHERE id_city=some_value)
Just an hint.
If you use the IN operator in a WHERE clause, you can hope that the short-circuit of operator AND may remove unnecessary JOINs during the execution for the tours that do not respect the constraint.
Seems like an odd way to do that query, here
SELECT t.name FROM `incoming_tours` as t WHERE t.id IN (SELECT id_parrent FROM `incoming_tours_cities` as tc WHERE tc.id_city IN ('4','74'));
I think that does it, but not tested...
EDIT: Added table alias to sub-query
I've written this query using CTE's and it includes the test data in the query. You'll need to modify it so that it queries the real tables instead. Not sure how it performs on a large dataset...
Declare #numCities int = 2
;with incoming_tours(id, name) AS
(
select 1, 'first_tour' union all
select 2, 'second_tour' union all
select 3, 'third_tour' union all
select 4, 'fourth_tour'
)
, incoming_tours_cities(id_parent, id_city) AS
(
select 1, 4 union all
select 1, 5 union all
select 1, 27 union all
select 1, 74 union all
select 2, 1 union all
select 2, 5
)
, cityIds(id_city) AS
(
select 4
union all select 5
/* Add all city ids you need to check in this table */
)
, common_cities(id_city, tour_id, tour_name) AS
(
select c.id_city, it.id, it.name
from cityIds C, Incoming_tours_cities tc, incoming_tours it
where C.id_city = tc.id_city
and tc.id_parent = it.id
)
, tours_with_all_cities(id_city) As
(
select tour_id from common_cities
group by tour_id
having COUNT(id_city) = #numCities
)
select it.name from incoming_tours it, tours_with_all_cities tic
where it.id = tic.id_city

Categories