whats wrong with this query? - sql

I'm trying to write a query that selects from four tables
campaignSentParent csp
campaignSentEmail cse
campaignSentFax csf
campaignSentSms css
Each of the cse, csf, and css tables are linked to the csp table by csp.id = (cse/csf/css).parentId
The csp table has a column called campaignId,
What I want to do is end up with rows that look like:
| id | dateSent | emailsSent | faxsSent | smssSent |
| 1 | 2011-02-04 | 139 | 129 | 140 |
But instead I end up with a row that looks like:
| 1 | 2011-02-03 | 2510340 | 2510340 | 2510340 |
Here is the query I am trying
SELECT csp.id id, csp.dateSent dateSent,
COUNT(cse.parentId) emailsSent,
COUNT(csf.parentId) faxsSent,
COUNT(css.parentId) smsSent
FROM campaignSentParent csp,
campaignSentEmail cse,
campaignSentFax csf,
campaignSentSms css
WHERE csp.campaignId = 1
AND csf.parentId = csp.id
AND cse.parentId = csp.id
AND css.parentId = csp.id;
Adding GROUP BY did not help, so I am posting the create statements.
csp
CREATE TABLE `campaignsentparent` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`campaignId` int(11) NOT NULL,
`dateSent` datetime NOT NULL,
`account` int(11) NOT NULL,
`status` varchar(15) NOT NULL DEFAULT 'Creating',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=latin1
cse/csf (same structure, different names)
CREATE TABLE `campaignsentemail` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`parentId` int(11) NOT NULL,
`contactId` int(11) NOT NULL,
`content` text,
`subject` text,
`status` varchar(15) DEFAULT 'Pending',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=140 DEFAULT CHARSET=latin1
css
CREATE TABLE `campaignsentsms` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`parentId` int(11) NOT NULL,
`contactId` int(11) NOT NULL,
`content` text,
`status` varchar(15) DEFAULT 'Pending',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=141 DEFAULT CHARSET=latin1

You need to aggregate the sums separately, not as shown in the question.
SELECT csp.id, csp.dateSent dateSent,
e.email_count, f.fax_count, s.sms_count
FROM campaignSentParent AS csp
JOIN (SELECT cse.ParentId, COUNT(*) AS email_count
FROM campaignSentEmail cse
GROUP BY cse.ParentID) AS e ON e.parentID = csp.id
JOIN (SELECT csf.ParentId, COUNT(*) AS fax_count
FROM campaignSentFax csf
GROUP BY csf.ParentID) AS f ON f.ParentID = csp.id
JOIN (SELECT css.ParentID, COUNT(*) AS sms_count
FROM campaignSentSms css
GROUP BY css.ParentId) AS s ON s.ParentID = csp.id
WHERE csp.campaignId = 1
To do this, you pretty much have to use the JOIN notation as shown.
You depending on the quality of your optimizer and the cardinalities of the various tables and the available indexes, you might find it effective to include a join with CampaignSentParent in each of the sub-queries with the csp.CampaignID = 1 condition, so as to limit the data aggregated by the sub-queries.
You might notice that the result count you get is 2510340. The prime factorization of 2510340 is 2 × 2 × 3 × 5 × 7 × 43 × 139, and your expected answer is 139, 129, and 140. You can get 3 × 43 = 129; 2 × 2 × 5 × 7 = 140; and 139 = 139. In other words, the original query is generating the Cartesian product of all the rows in the three dependent tables and counting the product, rather than counting the relevant rows from each dependent table separately.

You're missing a GROUP BY statement at the end. I can't tell from your example what you want them to be grouped by to actually give you the code.

Add GROUP BY dateSent to the end of your query.

Try adding a group by clause.
SELECT csp.id id, csp.dateSent dateSent,
COUNT('cse.parentId') emailsSent,
COUNT('csf.parentId') faxsSent,
COUNT('css.parentId') smsSent
FROM campaignSentParent csp,
campaignSentEmail cse,
campaignSentFax csf,
campaignSentSms css
WHERE csp.campaignId = 1
AND csf.parentId = csp.id
AND cse.parentId = csp.id
AND css.parentId = csp.id
GROUP BY csp.id, csp.dateSent
When you use an aggregate function, you normally need to include a group by.

Related

SQL comparison report on cartesian product using subquery

I'm a student building a comparison report query in MySQL on a database that tracks customers, products, and purchases in separate tables. I have to create a report that shows how many products were sold every month for each province using a subquery. I was told to use a cross join between product and customer, however, my query runs into a problem when I try to group them as the records all collapse into each other and I don't understand why this is happening. I'm not sure if this is the correct way to approach this problem since my customer and product table don't have any values that intersect with each other except through the purchase table.
These are my create table scripts
CREATE TABLE 'customer' (
'CustomerID' INT NOT NULL,
'City' VARCHAR(100) NOT NULL,
'Province' CHAR(2) NOT NULL,
PRIMARY KEY ('CustomerID'));
CREATE TABLE 'product' (
'ProductID' INT NOT NULL,
'ProductName' VARCHAR(100) NOT NULL,
'Price' DECIMAL(5,2) NOT NULL,
PRIMARY KEY ('ProductID'));
CREATE TABLE 'purchase' (
'PurchaseID' INT NOT NULL,
'PurchaseDate' DATE NOT NULL,
'customer_CustomerID' INT NOT NULL,
'product_ProductID' INT NOT NULL,
PRIMARY KEY ('PurchaseID'),
CONSTRAINT 'fk_purchase_customer'
FOREIGN KEY ('customer_CustomerID')
REFERENCES 'customer' ('CustomerID'),
CONSTRAINT 'fk_purchase_product'
FOREIGN KEY ('product_ProductID')
REFERENCES 'product' ('ProductID'));
This is the query that I have written as I have understood the instructions.
SELECT DISTINCT province, productName AS Product, JanTotalSales
FROM PRODUCT cross join CUSTOMER
LEFT JOIN
(
SELECT purchaseID, product_productID, customer_customerID, COUNT(purchaseDate) AS JanTotalSales
FROM PURCHASE
WHERE MONTH(purchaseDate) = 01
)JAN ON PRODUCT.productID = JAN.product_productID
GROUP BY province, productID;
I should be getting results like this
Province
Product
JanTotalSales
FebTotalSales
...
TotalSales
QC
Paper
1
NULL
...
1
ON
Paper
1
2
...
3
AB
Paper
1
NULL
...
1
AB
Wire
2
2
...
4
ON
Wire
2
1
...
3
NULL
Kit
NULL
NULL
...
NULL
SK
Gummy
1
1
...
2
NULL
Bag
NULL
NULL
...
NULL
However, I receive results like this when I do it on the January subquery.
Province
Product
JanTotalSales
AB
Paper
NULL
AB
Wire
NULL
AB
Kit
NULL
AB
Kit
13
ON
Paper
NULL
ON
Wire
NULL
ON
Kit
NULL
ON
Kit
13
I appreciate whatever help you can give to show me where I'm going wrong. From what I understand it's something to do with how the grouping occurs but I can't figure out why.

Joining column for multiple tables

I am trying to extract two same-data-type columns from two different tables using one query. NOTE: Accounts attribute length in both table varies. Union can't work here because number of columns are (in reality) different in both tables.
CREATE TABLE IF NOT EXISTS `mydb`.`TABLE_A` (
`ID_TABLE_A` INT NOT NULL AUTO_INCREMENT,
`ACCOUNT` VARCHAR(5) NULL,
`SALES` INT NULL,
PRIMARY KEY (`ID_TABLE_A`))
ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS `mydb`.`TABLE_B` (
`ID_TABLE_B` INT NOT NULL AUTO_INCREMENT,
`ACOUNT` VARCHAR(9) NULL,
`SALES` INT NULL,
PRIMARY KEY (`ID_TABLE_B`))
ENGINE = InnoDB;
Requirement:(I know this can't be right but just to demonstrate a partial picture)
SELECT
ACCOUNTS,
SALES
FROM
TABLE_A, TABLE_B
Result:
---------------
|accounts|sales|
| 2854 |52500 |
| 6584 |54645 |
| 54782| 5624 |
| 58496|46259 |
| 56958| 6528 |
---------------
If you want the union of two tables that are not union-compatible, then make them union-compatible:
(SELECT
ACCOUNTS,
SALES
FROM
TABLE_A) UNION ALL
(SELECT
ACCOUNTS,
SALES
FROM TABLE_B)
I put the UNION ALL based on the assumption that you would like to keep duplicates. If you would like the output to be duplicate-free, replace it with UNION.

Using ORDER BY and LIMIT in an SQL view

I'm trying to create a view that is limited to the last entry per id
My table structure is as follows
CREATE TABLE IF NOT EXISTS `u_tbleeditlog` (
`editID` bigint(20) NOT NULL AUTO_INCREMENT,
`editType` int(1) NOT NULL,
`editTypeID` bigint(20) NOT NULL,
`editedID` bigint(20) NOT NULL,
`editedDtm` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`editID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
And I'm trying to create a view that will only display the last entry assigned to the Type and TypeID
My view so far
CREATE OR REPLACE VIEW vwu_editlog AS
SELECT u_tbleeditlog.*, CONCAT_WS(' ',u_users.user_firstname,u_users.user_lastname) AS editedEditor
FROM u_tbleeditlog
JOIN u_users ON u_users.user_id = u_tbleeditlog.editedID
ORDER BY u_tbleeditlog.editedDtm DESC LIMIT 1
But my problem is that this limits the entire view to just 1 result overall, and I get the message Current selection does not contain a unique column. Grid edit, checkbox, Edit, Copy and Delete features are not available.
So say there are multiple values with 1, 1, 2017-08-16, 1, 1, 2016-05-14 etc it will only return 1, 1, 2017-08-16
Can anyone please tell me if what I'm trying to do is possible, and if so how? :)
Do this with the not exists approach to getting the last row in a series:
CREATE OR REPLACE VIEW vwu_editlog AS
SELECT el.*, CONCAT_WS(' ', u.user_firstname, u.user_lastname) AS editedEditor
FROM u_tbleeditlog el JOIN
u_users u
ON u.user_id = el.editedID
WHERE not exists (select 1
from u_tbleeditlog el2
where el2.editType = el.editType and
el2.editTypeID = el.editTypeID and
el2.editedDtm > el.editedDtm
);
You have to use GROUP BY and HAVING() for that. What database are you using?
It should look something like this:
SELECT editType, editedDtm
FROM u_tbleeditlog AS u
GROUP BY editType, editedDtm
HAVING editedDtm = (SELECT MAX(editedDtm) FROM u_tbleeditlog WHERE editType = u.editType)
ORDER BY editedDtm DESC

many-to-many query

I have following database structure,
CREATE TABLE IF NOT EXISTS `analyze` (
`disease_id` int(11) NOT NULL,
`symptom_id` int(11) NOT NULL
) ;
CREATE TABLE IF NOT EXISTS `disease` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(10) NOT NULL,
PRIMARY KEY (`id`)
) ;
CREATE TABLE IF NOT EXISTS `symptom` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(4) NOT NULL,
PRIMARY KEY (`id`)
) ;
EDIT:
Sorry, I mean how do I identify the disease from inputted symptoms.
Example:
If I have symptom: fever and cough then I would have influenza.
If I have symptom: sore throat and fever then I would have throat infection.
The input are $symptom1, $symptom2, $symptom3, and so on.
Thank you.
SELECT disease_id
FROM analyze
GROUP BY disease_id
HAVING COUNT(symptom_id) > 1
Edit: to reply to the edited question
SELECT disease_id, COUNT(DISTINCT symptom_id)
FROM analyze
WHERE symptom_id IN ($symptom1, $symptom2, $symptom3)
GROUP BY disease_id
ORDER BY COUNT(DISTINCT symptom_id) DESC
Of course you'll have to replace $symptomX by their respective ID's.
This query lists the diseases which match at least one symptom - the diseases which match the most symptoms are on top.
If you added an unique constraint on symptom_id and disease_id in analyze, you could lose the DISTINCT:
SELECT disease_id, COUNT(symptom_id)
FROM analyze
WHERE symptom_id IN ($symptom1, $symptom2, $symptom3)
GROUP BY disease_id
ORDER BY COUNT(symptom_id) DESC
select d.id from disease d inner join analyze a
on d.id = a.disease_id
group by d.id having count(a.disease_id) > 1
select disease_id, count(*)
from analyze
where symptom_id in ($symptom1, $symptom2, $symptom3)
group by disease_id
order by 2 descending;
will return the matching disease ids in descending order of matching symptoms.

Mysql selecting rows from multiple tables

I'm working on a catalog site where users can browse categories. Categories can contain other categories and products, and products can belong to more than one category. The relevant database schema looks something like this:
CREATE TABLE products (
product_id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
product_title VARCHAR(100) NOT NULL,
product_status TINYINT UNSIGNED NOT NULL
);
CREATE TABLE product_categories (
category_id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
parent_category_id INT UNSIGNED NOT NULL,
category_title VARCHAR(100) NOT NULL,
category_status TINYINT UNSIGNED NOT NULL,
category_order INT UNSIGNED NOT NULL
);
CREATE TABLE products_categories (
product_id INT UNSIGNED NOT NULL,
category_id INT UNSIGNED NOT NULL,
product_order INT UNSIGNED NOT NULL,
PRIMARY KEY(product_id, category_id)
);
The issue i'm having is I need to paginate the results using LIMIT n, n:
$perpage = 20;
$start = (isset($_GET['page'])) ? (int)$_GET['page'] * $perpage : 1;
$limitsql = "LIMIT $start, $perpage";
But I can't figure out how to select both distinct categories and products without joining and merging the results. Ideally I would like results like this:
product_id | product_title | category_id | category_title
NULL | NULL | 32 | category foo
NULL | NULL | 239 | category bar
9391 | product foo | NULL | NULL
325 | product bar | NULL | NULL
The best I've been able to do is get something like this, which doesn't really help:
product_id | product_title | category_id | category_title
9391 | product foo | 32 | category foo
325 | product bar | 239 | category bar
239 | product foo2 | 32 | category foo
115 | product bar2 | 239 | category bar
The only other solutions that I can think of would be to query all subcategories and products within the category, stick them in a php array and extract the current page with array_slice. Considering the volume of products (several thousand) this isn't a very appealing option.
Otherwise I could query the number of categories, and offset the $start in the LIMIT clause by the number of categories. This get's messy though if there is more than a full page of categories.
Here is my current working query which gives me the results above:
SELECT
p.product_id, p.product_title,
c.category_id, c.category_title
FROM products AS p
JOIN product_categories AS c
ON c.parent_category_id='20'
INNER JOIN products_categories AS pc
ON p.product_id=pc.product_id
WHERE p.product_status='1' AND pc.category_id='20'
ORDER BY pc.product_order ASC
Edit
I think i've got it working with UNION, which I completely forgot about
SELECT
c.category_id AS row_id, c.category_title AS row_title, 1 AS is_category
FROM product_categories AS c
WHERE c.parent_category_id='20'
UNION
SELECT
p.product_id AS row_id, p.product_title AS row_title, 0 AS is_category
FROM products AS p
INNER JOIN products_categories AS pc
ON p.product_id=pc.product_id
Edit 2
I guess Union isn't going to work as I thought. Since both are treated as separate queries I can't apply LIMIT to the entire result, only each individual SELECT. Also it seems the columns selected from each statement must be of the same type of the corresponding type in the other statement.
Use:
SELECT *
FROM (SELECT c.category_id AS row_id, c.category_title AS row_title, 1 AS is_category
FROM product_categories AS c
WHERE c.parent_category_id='20'
UNION
SELECT p.product_id AS row_id, p.product_title AS row_title, 0 AS is_category
FROM products AS p
JOIN products_categories AS pc ON p.product_id=pc.product_id) x
LIMIT x, y
Another way you could approach this would be changing your schema to make categories and products the same thing essentially.
CREATE TABLE items (
item_id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
item_title VARCHAR(100) NOT NULL,
item_status TINYINT UNSIGNED NOT NULL,
category_or_item TINYINT UNSIGNED NOT NULL,
);
CREATE TABLE items_parents (
item_id INT UNSIGNED NOT NULL,
parent_id INT UNSIGNED NOT NULL, #points to itemid
item_order INT UNSIGNED NOT NULL,
PRIMARY KEY(item_id, parent_id)
);
Your query then is flat and you can sort it by category_or_item so categories appear first.