I have a table with a field (cat_tags) which contain the following rows:-
id - cat_tags - date_added
1 - yam, potato, onion, pepper,beans - 23-12-2011
row1 - yam, potato, onion, pepper 23-12-2011
row2 - onion, pepper, beans - 23-12-2011
row3 - pepper, beans, rice - 23-12-2011
row4 - rice, potato, pepper, yam - 23-12-2011
row5 - beans, rice, onion, food - 23-12-2011
.....
.....
.....
pls how can I get a DISTINCT list of items and the number of times(frequency) they occurred in the entire table field?
Eg. pepper x 4
onion x 3
yam x 2
thank you in advance
First normalize your table(s).
Table tags
id unsigned integer autoincrement primary key,
tag varchar(40) not null,
unique index tag(tag)
Table Item_tags
id unsigned integer autoincrement primary key,
tag_id integer not null,
item_id integer not null
Table items
id unsigned integer autoincrement primary key,
date_added date not null
Insert an item into your database using the following series of queries:
INSERT INTO Items VALUES (null, NOW());
SELECT #last_item_id:= LAST_INSERT_ID();
REPLACE IGNORE INTO tags (tag) VALUES ('pepper', 'onion', 'rice')
INSERT INTO Item_tags
SELECT
null as id
, tags.id
, last_item_id
FROM tags WHERE tags.tag IN ('pepper', 'onion', 'rice')
If you want to simplify the addition of items and streamline your php code,
use a blackhole table and a trigger on that table.
CREATE TABLE bh_items (
id unsigned integer autoincrement primary key,
date_added timestamp,
tag1 varchar(45) not null,
tag2 varchar(45) default null,
tag3 varchar(45) default null,
tag4 varchar(45) default null,
tag5 varchar(45) default null,
tag6 varchar(45) default null,
tag7 varchar(45) default null,
tag8 varchar(45) default null,
tag9 varchar(45) default null,
tag10 varchar(45) default null) ENGINE = blackhole;
DELIMITER $$
CREATE TRIGGER ai_bn_items_each AFTER INSERT ON bh_items FOR EACH ROW
BEGIN
DECLARE last_item_id integer;
INSERT INTO Items VALUES (null, new.date_added);
SELECT LAST_INSERT_ID() INTO last_item_id;
REPLACE IGNORE INTO tags ((null, new.tag1)
,(null, new.tag2)
,(null, new.tag3)
,(null, new.tag4)
,(null, new.tag5)
,(null, new.tag6)
,(null, new.tag7)
,(null, new.tag8)
,(null, new.tag9)
,(null, new.tag10));
INSERT IGNORE INTO item_tags (item_id, tag_id)
SELECT last_item_id, tags.tag FROM tags
WHERE tags.tag
IN (new.tag1, new.tag2, new.tag3, new.tag4, new,tag5
, new.tag6, new.tag7, new.tag8, new.tag9, new.tag10);
END $$
DELIMITER ;
If you use the trigger you can just insert into the blackhole table and the trigger will then update all your 3 tables.
INSERT INTO bh_items VALUES (null, null, 'onion', 'rice', 'pepper'
, null, null, null, null, null, null, null)
All your tables will be automagically updated.
Back to your question
You can then select from this using this query:
SELECT tags.tag, COUNT(*) as freq FROM item_tags
INNER JOIN tags ON (item_tags.tag_id = tags.id)
GROUP BY item_tags.tag_id
Links:
blackhole: http://dev.mysql.com/doc/refman/5.1/en/blackhole-storage-engine.html
replace into: http://dev.mysql.com/doc/refman/5.1/en/replace.html
Triggers: http://dev.mysql.com/doc/refman/5.1/en/triggers.html
Create table: http://dev.mysql.com/doc/refman/5.1/en/create-table.html
If I have understood your question correctly, this should work:
$result = mysql_query("SELECT cat_tags FROM table");
while($row = mysql_fetch_array($result))
{
//Split the row into an array based on the comma
$detail_array = explode(",", $row['cat_tags']);
//Go through that array and index a master count for each occurence of the same value
//Trim due to the spaces after the comma in your field
foreach($detail_array as $key=>$val)
{
$output_array[trim($val)] = $output_array[trim($val)] + 1;
}
}
print_r($output_array);
You should first normalize your table structure.
It is not compliant with the first normal form, making the requested operation way more complicated than it needs to be.
Related
I have two tables, identical in structure. First one is populated with records obtained from another system that potentially needs corrections (could be one or many columns). Second table is corrections that I want to apply to the first table.
For example, I could have the following rows on table1:
order_number | name | email | tracking_no
101 null foo#bar.com 3456789
102 An Example ex#ample.com 1010101
...and the corrections I want to apply to these rows on table2:
order_number | name | email | tracking_no
101 Name Surname null null
102 null null 45778901
Essentially: Add missing name to order_number 101 and correct the wrong tracking_no for order_number 102.
The logic of what I am trying to do is: "Patch" values in table1 with the corrections contained for the same order_number in table2, giving precedence over to values in table2 and not overwriting existing values in table1 if the corresponding value in table2 is a null.
For the case where a value is null in table1 and we have a non-null correction in table2, COALESCE seems to be the right way to go but I can't figure out how to overwrite an already existing value with the corresponding "fix" from table2.
Is there a mechanism in MySQL/MariaDB that would allow me to do this as the alternative is a very messy "pull two records from two tables, compare values and build up the new correct record and insert it back into table1".
As pointed out in comments, here is a reproducible set of test data along with table structures:
USE so_demo;
DROP TABLE IF EXISTS so_demo.table1;
DROP TABLE IF EXISTS so_demo.table2;
CREATE TABLE so_demo.table1 (
order_number int(11) NOT NULL AUTO_INCREMENT,
name varchar(50) DEFAULT NULL,
email varchar(250) DEFAULT NULL,
tracking_no varchar(255) DEFAULT NULL,
PRIMARY KEY (order_number),
UNIQUE INDEX UK_table1_order_number (order_number)
)
ENGINE = INNODB,
CHARACTER SET utf8mb4,
COLLATE utf8mb4_general_ci;
CREATE TABLE so_demo.table2 (
order_number int(11) NOT NULL AUTO_INCREMENT,
name varchar(50) DEFAULT NULL,
email varchar(250) DEFAULT NULL,
tracking_no varchar(255) DEFAULT NULL,
PRIMARY KEY (order_number),
UNIQUE INDEX UK_table2_order_number (order_number)
)
ENGINE = INNODB,
CHARACTER SET utf8mb4,
COLLATE utf8mb4_general_ci;
INSERT INTO so_demo.table1 VALUES (101, NULL, "foo#bar.com", 3456789);
INSERT INTO so_demo.table1 VALUES (102, "An Example", "ex#ample.com", 1010101);
INSERT INTO so_demo.table2 VALUES (101, "Name Surname", NULL, NULL);
INSERT INTO so_demo.table2 VALUES (102, NULL, NULL, 45778901);
The term you are looking for is merge and in mysql an insert on duplicate key
drop table if exists t,t1;
create table t(order_number int primary key, name varchar(20) , email varchar(20), tracking_no int);
insert into t values
(101 , null , 'foo#bar.com' , 3456789),
(102 , 'An Example', 'ex#ample.com' , 1010101);
create table t1(order_number int, name varchar(20), email varchar(20), tracking_no int);
insert into t1 values
(101 , 'Name Surname', null , null),
(102 , null , null , '45778901');
insert into t
select * from t1
on duplicate key update
t.name = case when t1.name is not null then t1.name else t.name end,
t.email = case when t1.email is not null then t1.email else t.email end,
t.tracking_no = case when t1.tracking_no is not null then t1.tracking_no else t.tracking_no end
;
select * from t;
+--------------+--------------+--------------+-------------+
| order_number | name | email | tracking_no |
+--------------+--------------+--------------+-------------+
| 101 | Name Surname | foo#bar.com | 3456789 |
| 102 | An Example | ex#ample.com | 45778901 |
+--------------+--------------+--------------+-------------+
2 rows in set (0.001 sec)
quite a few hours struggling with a problem, it's more about the issue.
Depicting the script when you type in textboxa searches in SQL data records where name = contain. Assuming that I entered 2 names separated by a comma (X, Y) 2:
a) If you choose "all containing typed values" is to look for all the records, in the above "name". It works:
$where. = "AND name IN ('". implode (' ', ' ', $array). "')";
Model: Contains X.
Contains The Y;
Contains the Y and X;
It works as it should.
(b)) If you choose "all containing only the values to be entered to look for records in which only occurs in the" name "is what we have.
Here I do not know how to do it.
Model: contains the X and Y-only, I don't want records that contain only X, but Y and Alternatively, if in MySQL "name" is 3 options (X, Y, Z).
I do not know how to explain more clearly:--------------------example: I have 5 values in the table, where the name is in different combinations:
1) Name1, Name2,
2) name1, Name8,
3) name1, Name9,
4) Name8, Name3,
5) Name4, Name5,
using the selected the first option, where the textbox typed: "Name1, Name" the result will be: 1), 2), 3), 4), this is valid.
Using the second result should only be 2).
The result of a) res1 or res2 or res1 and res2.
Now I need somehow to come to a solution, when I type in textbox "res1, res2" the result will be: only the records that contain res1 and res2, and NOTHING AFTER that.
---- EDIT FOR REPLIES:
+--------------------- my_val_search -----------+
| aid | int(11) | primary key auto_increment|
|name | varchar(255)| |
+-----------------------------------------------+
Records:
1|Ruby
2|CSS
3|HTML
4|PHP
5|Python
6|SQL
7|Javascript
8|C++
9|AJAX
10|Java
+---------------------- topics -----------------+
|id | int(11) | Primary key auto_increment |
|dateline| date | |
|author | varchar(30) | |
|message | text | |
|aid |int(11) |foreign key with my_val_search|
+-----------------------------------------------+
Now if you type in the search "Ruby, Python" I want to have found records only for topics where are "Ruby and Python".
By using the
$myValInput = $_GET['keywords'];
$where = "AND a.name IN('" . implode("','", $myValInput. "')";
Searches for records, where when you type "Python, Ruby" is divided into:
-Python
-Ruby,
- Python and Ruby
and I want to make the results appear only for "Python and Ruby". In addition, if someone types "Python, Ruby, Java," the result should be
-Python, Ruby, Java,
the record should be just what I wrote, nothing more.
Using IN cannot get what you expect!
When used with a subquery, the word IN is an alias for = ANY. Thus, these two statements are the same:
SELECT s1 FROM t1 WHERE s1 = ANY (SELECT s1 FROM t2);
SELECT s1 FROM t1 WHERE s1 IN (SELECT s1 FROM t2);
https://dev.mysql.com/doc/refman/5.7/en/any-in-some-subqueries.html
Besides, I think your schema is incorrect. The topic should be parent and the topic keywords should be child. If I were you, I will do it like this.
CREATE TABLE IF NOT EXISTS `topic` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`dateline` date,
`author` varchar(30),
`message` text,
PRIMARY KEY(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;
CREATE TABLE IF NOT EXISTS `topic_keywords` (
`topic` int(10) unsigned NOT NULL,
`keyword` varchar(255),
FOREIGN KEY (`topic`) REFERENCES `topic` (`id`),
UNIQUE INDEX (`topic`, `keyword`)
) ENGINE=InnoDB;
In your case, I think the full text is suitable.
https://dev.mysql.com/doc/refman/5.7/en/fulltext-boolean.html
CREATE TABLE IF NOT EXISTS `topic` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`dateline` date,
`author` varchar(30),
`message` text,
`keywords` varchar(255),
PRIMARY KEY(`id`),
FULLTEXT (`keywords`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;
INSERT INTO `topic` (`keywords`)
VALUES ('Ruby,Python'),
('Ruby'),
('Ruby,SQL'),
('Python'),
('Python,SQL'),
('Ruby,Python,SQL,AJAX');
SELECT `keywords`
FROM `topic`
WHERE MATCH(`keywords`) AGAINST('+Ruby +Python' IN BOOLEAN MODE);
+----------------------+
| keywords |
+----------------------+
| Ruby,Python |
| Ruby,Python,SQL,AJAX |
+----------------------+
2 rows in set (0.00 sec)
If you just find out the records just exact your input. You can add a hash column. Like design a hash table for your Mysql table. So you can use hash value in your query statement. But don't forget to update the hash value, if your keywords value had been changed. Add the before update trigger might be a good choice.
CREATE TABLE IF NOT EXISTS `topic` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`dateline` date,
`author` varchar(30),
`message` text,
`keywords` varchar(255),
`keywords_hash` varchar(32),
PRIMARY KEY(`id`),
FULLTEXT (`keywords`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;
INSERT INTO `topic`
(`keywords` , `keywords_hash`)
VALUES ('Ruby,Python', md5('Ruby,Python'));
SELECT `keywords`
FROM `topic`
WHERE `keywords_hash` = md5('Ruby,Python');
Here's an attempt at a solution, but there's better solutions query wise than using a regular denormalized structure to handle this - so if you're going to query this a lot (and with a large number of terms), you might want to look into caching the lookup values in your original table and then querying by their content. Anyway, to solve it with regular joins:
Given data:
CREATE TABLE foo (id int);
CREATE TABLE foo_bar (foo_id int, bar_id int);
CREATE TABLE bar (id int, name varchar(25));
INSERT INTO foo VALUES (1), (2), (3), (4);
INSERT INTO bar VALUES (1, 'Ruby'), (2, 'Python'), (3, 'PHP');
INSERT INTO foo_bar VALUES (1, 1), (2, 1), (3, 3), (4, 3), (2,2);
You can retrieve id values from foo that has both Ruby and Python by joining each term manually:
SELECT f.id FROM foo f
JOIN foo_bar fb ON fb.foo_id = f.id
JOIN bar b ON fb.bar_id = b.id AND b.name = 'Ruby'
JOIN foo_bar fb2 ON fb2.foo_id = f.id
JOIN bar b2 ON fb2.bar_id = b2.id AND b2.name = 'Python'
This gives 2 as the only id that haves all languages present.
I have three tables
tbl_project
db_projectid db_projectname
1 test
2 xxx
tbl_activities
db_id db_projectid db_category
1 1 Civil Work
2 1 Mechanical
3 1 Electrical
tbl_dailypercentage
db_dpid db_aid db_projectid db_status
1 1 1 red
2 1 1
3 2 1
db_projectid is a primary key in tbl_project
db_id is a primary key and db_projectid is a foreign key in tbl_activities
db_dpid is a primary key and db_aid is a foreign key in tbl_dailypercentage
I tried this query
select
activities.db_id,
activities.db_category as cat,
dailypercentage.db_status,
dailypercentage.db_aid,
dailypercentage.db_projectid
from tbl_activities as activities,tbl_dailypercentage as dailypercentage
where
dailypercentage.db_projectid='$projectId'
and
activities.db_id=dailypercentage.db_aid
and
dailypercentage.db_status='red'
But i Have ab error
Undefined variable: cat
while($row=mysqli_fetch_array($statusQuery)){
$status=$row['db_status'];
$cat=$row['cat'];
}
<td> <?php if($cat=="Civil Work" && $status="red"){
echo"<p style='color:$status'>".($sumcivil/$civilCount)."</p>";}
else{echo ($sumcivil/$civilCount);}?>
</td>
I try also many thing the left join and the right join
to have the result i want
The Result i want is
For Project who have an id=1
category status
civil work red
mechanical work
electrical work
For Project who have an id=2
category status
civil work
mechanical work
electrical work
You showed this code:
while($row=mysqli_fetch_array($statusQuery)){
$status=$row['db_status'];
$cat=$row['cat'];
}
If your query returns no rows, $status and $cat will never get defined. I guess this is what went wrong. What happens if you run that query with a MySQL client like phpMyadmin?
Similarly, if your query returns more than one row, $status and $cat will capture the values of only the last row.
It's important to work out what you want to happen with no rows and multiple rows.
Based only on the tables in your question I have put together the following schema (I've not added constraints to the foreign keys as this is just for testing).
create table tbl_project (
`db_projectid` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`db_projectname` VARCHAR(255) NOT NULL DEFAULT 'Undefined',
PRIMARY KEY (`db_projectid`)
);
create table tbl_activities (
`db_id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`db_projectid` INT(1) NOT NULL,
`db_category` VARCHAR(255) NOT NULL DEFAULT 'Undefined',
PRIMARY KEY (`db_id`)
);
create table tbl_dailypercentage (
`db_dpid` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`db_aid` INT(1) NOT NULL,
`db_projectid` INT(1) NOT NULL,
`db_status` VARCHAR(255) NULL,
PRIMARY KEY (`db_dpid`)
);
insert into tbl_project (db_projectname) values ('Test'), ('xxx');
insert into tbl_activities (db_projectid, db_category) values ('1', 'Civil Work'), ('1', 'Mechanical'), ('1', 'Electrical');
insert into tbl_dailypercentage (db_aid, db_projectid, db_status) values
('1', '1', 'red'),
('1', '1', null),
('2', '1', null);
select a.db_category as cat, dp.db_status from tbl_project p
left join tbl_activities a on p.db_projectid = a.db_projectid
left join tbl_dailypercentage dp on dp.db_aid = a.db_id
where p.db_projectid = 1
group by a.db_id;
I've had to add the group by into the final query as you have multiple rows in tbl_dailypercentage with the same db_aid and db_projectid values.
After that, you want to tweak your php code, putting the td stuff inside the while loop:
<?php while($row=mysqli_fetch_array($statusQuery)):?>
$status=$row['db_status'];
$cat=$row['cat'];
<td> <?php if($cat=="Civil Work" && $status="red"){
echo"<p style='color:$status'>".($sumcivil/$civilCount)."</p>";}
else{echo ($sumcivil/$civilCount);}?>
</td>
<?php endwhile; ?>
So I know that there are several many posts on this topic on this website, and the closest one I could find that was similar was:
Can I take the results from two rows and combine them into one?
I am working on a project that involves 'accounts receivables' and 'accounts payable', but that both of those need data in a single list:
date | description | reference | debit | credit
I have read about the mySQL UNION statement being used to combine two result sets into one, however, it also appears that the two results sets must match in column count and type according to the below website:
http://www.w3schools.com/sql/sql_union.asp
The problem I'm facing is that the two result sets don't have the same column count as the information for one doesn't directly correlate to the other (which will exclude the use of the UNION statement). What would be the best practice at acquiring the data from the two tables and sort them based on date? I'll include my SQL calls below as reference:
Accounts Receivable:
SELECT tblARP.*,tblAR.invoiceID,tblAR.ledgerID
FROM Accounting_ReceivablesPayments tblARP
INNER JOIN Accounting_Receivables tblAR ON tblARP.invoiceID = tblAR.invoiceID
ORDER BY deposited
Accounts Payable:
SELECT tblAPP.*,tblAP.id,tblAP.ledgerID,tblAP.tblName,tblAP.rowID,tblAP.invoice
FROM Accounting_PayablesPayments tblAPP
INNER JOIN Accounting_Payables tblAP ON tblAPP.payablesID = tblAP.id
ORDER BY deposited
UPDATE
Per the requests in the comments, here are the columns for the tables:
Accounting_Receivables
id BIGINT PRIMARY KEY NOT NULL AUTO_INCREMENT UNIQUE,
invoiceID BIGINT NOT NULL,
amount DECIMAL(9,2) NOT NULL DEFAULT '1.00',
ledgerID BIGINT NOT NULL,
note TEXT
Accounting_ReceivablesPayments
id BIGINT PRIMARY KEY NOT NULL AUTO_INCREMENT UNIQUE,
invoiceID BIGINT NOT NULL,
received DATE NOT NULL,
type VARCHAR(10) NOT NULL,
amount DECIMAL(9,2) NOT NULL DEFAULT '1.00',
deposited DATE,
tag VARCHAR(32) NOT NULL
Accounting_Payables
id BIGINT PRIMARY KEY NOT NULL AUTO_INCREMENT UNIQUE,
paid TINYINT(1) UNSIGNED NOT NULL DEFAULT '0',
invoice BIGINT NOT NULL,
amount DECIMAL(9,2) NOT NULL DEFAULT '1.00',
terms VARCHAR(3) NOT NULL DEFAULT 'net',
due DATE,
tblName VARCHAR(48) NOT NULL,
rowID BIGINT NOT NULL,
ledgerID BIGINT NOT NULL,
note TEXT
Accounting_PayablesPayments
id BIGINT PRIMARY KEY NOT NULL AUTO_INCREMENT UNIQUE,
payablesID BIGINT NOT NULL,
created DATE NOT NULL,
type VARCHAR(10) NOT NULL,
amount DECIMAL(9,2) NOT NULL DEFAULT '1.00',
deposited DATE,
tag VARCHAR(32) NOT NULL
to what I was saying in the comments you should do this
( SELECT
tblARP.*,
tblAR.invoiceID,
tblAR.ledgerID,
NULL, -- # -- null values for your rows to match columns
NULL,
NULL
FROM `Accounting_ReceivablesPayments` tblARP
INNER JOIN `Accounting_Receivables` tblAR ON tblARP.invoiceID = tblAR.invoiceID
ORDER BY deposited
)
UNION ALL -- # -- union all to include everything
( SELECT
tblAPP.*,
tblAP.id,
tblAP.ledgerID,
tblAP.tblName,
tblAP.rowID,
tblAP.invoice
FROM `Accounting_PayablesPayments` tblAPP
INNER JOIN `Accounting_Payables` tblAP ON tblAPP.payablesID = tblAP.id
ORDER BY deposited
)
I have two tables one containing a selection of values in different categories and the other ‘master’ table referencing the text values by the first primary key.
Table 1
CREATE TABLE IF NOT EXISTS `defaultvalues` (
`default_ID` int(11) NOT NULL AUTO_INCREMENT,
`columnName` varchar(100) NOT NULL,
`defaultValue` varchar(100) NOT NULL,
PRIMARY KEY (`default_ID`),
UNIQUE KEY `columnName` (`columnName`,`defaultValue`)
)
Table 2
CREATE TABLE IF NOT EXISTS `master` (
`master_ID` int(11) NOT NULL AUTO_INCREMENT,
`size` int(11) NOT NULL,
`madeby` int(11) NOT NULL,
`type` int(11) NOT NULL,
`colour` int(11) NOT NULL,
`notes` text NOT NULL,
`issueDate` datetime NOT NULL,
`ceMark` text NOT NULL,
`batchNumber` text NOT NULL,
PRIMARY KEY (master_ID)
)
The master.size for each row is a P.key in the defaultvalues table.
E.g. master.colour = 234, 234=defaultvalues.defaultValue = ‘red’
E.g. master.size = 345, 345=defaultvalues.defaultValue = ‘small’
Now I would like to run a query that returns the ‘master’ table with text values in columns colour, size, type, madeby from ‘defaultvalues. defaultValue’ and ready for further processing.
I have been trying with sub queries and temp tables but I can’t get it to work
The current system relies on PHP and multiple queries and building arrays.
There has to be a more elegant solution.
I hope this makes sense.
Any hints or advice much appreciated.
Dave
You'll need to join the master table to the defaultvalues table multiple times. Something like this:
SELECT m.*, d.defaultvalue as sizevalue, d2.defaultvalue as colorvalue...
FROM master m
JOIN defaultvalues d ON m.size = d.default_id
JOIN defaultvalues d2 ON m.color = d2.default_id
...
What i did in the end.... while it works I am still not happy. There must be something better...
SELECT m.*,
(SELECT defaultValue FROM defaultvalues WHERE default_ID = m.colour) AS myColour ,
(SELECT defaultValue FROM defaultvalues WHERE default_ID = m.size) AS mySize
FROM master m
WHERE m.master_ID = 1;