Search query for multiple tables with count(*)? - php

I have a search field for an online shop that uses MySQL query for 2 different tables. The first table is vinyls which has 13 columns, and the second table is products which has 10 columns. The result I want is that if you enter a keyword, it will search the album_name and artist_name columns of table vinyls, and the name column in table products to find any matches.
My php code first counts the number of times a hit is made, and if it does, uses a query again to display the hits like online stores do. However, when I tested it, it only shows results from the vinyls table and not the products table. I've tried using UNION but because both tables have a different number of columns, it results in an error. I've used NULL to fill up the missing columns needed for union but it doesn't work too. Joins doesn't help either as the two tables do not have any similar column. What complicated it is the use of count(*) for the first query.
My SQL query using UNION and COUNT:
(SELECT COUNT(*) AS numrows
FROM vinyls
WHERE ( album_name LIKE :keyword
OR artist_name LIKE :keyword)
)
union
(SELECT COUNT(*) AS numrows
FROM products
WHERE name LIKE :keyword)
My php code for search field:
$stmt = $conn->prepare("(SELECT COUNT(*) AS numrows
FROM vinyls
WHERE ( album_name LIKE :keyword
OR artist_name LIKE :keyword)
)
union
(SELECT COUNT(*) AS numrows
FROM products
WHERE name LIKE :keyword)");
$stmt->execute(['keyword' => '%'.$_POST['keyword'].'%']);
$row = $stmt->fetch();
if($row['numrows'] < 1){
echo '<h1 class="page-header">No results found for <i>'.$_POST['keyword'].'</i></h1>';
}else{
echo '<h1 class="page-header">Search results for <i>'.$_POST['keyword'].'</i></h1>';
try{
$inc = 3;
$stmt = $conn->prepare("(SELECT *
FROM vinyls
WHERE ( album_name LIKE :keyword
OR artist_name LIKE :keyword)
)
union
(SELECT *
FROM products
WHERE name LIKE :keyword)");
$stmt->execute(['keyword' => '%'.$_POST['keyword'].'%']);
foreach ($stmt as $row) {...
I'm already stumped on how to search the 2 tables from only one search field and display the results from both tables and not only just one table. And it's a similar problem too when adding items to cart as they can be either from the two tables and I have to "join" the two tables again.
Where did I go wrong?
Edit:
Sample data
Vinyls table
+----+------------------+---------------------------+-----------+
| id | album_name | artist_name | genre |
+----+------------------+---------------------------+-----------+
| 11 | Ravel Bolero | Boston Symphony Orchestra | Classical |
| 12 | TV Calendar Show | Arthur Godfrey | Orchestra |
| 13 | Flip Phillips | The Phillips Quartet | Jazz |
| 14 | The Modern Idiom | Various artists | Jazz |
+----+------------------+---------------------------+-----------+
Products table
+----+-------------+------------------------------------------------------+
| id | category_id | name |
+----+-------------+------------------------------------------------------+
| 31 | 15 | Tonka/Diecast trucks and cars |
| 32 | 21 | Plate Number Georgia PQQ 1151 |
| 33 | 6 | Walt Disney Read Along Casette Tapes |
| 34 | 7 | Herbert Von Karajan |
| 35 | 8 | BANK IT! Board Game |
| 36 | 9 | Iraqi Most Wanted Playing Cards |
| 37 | 10 | STAR TREK ( 1991,'92,'93 ) Stamp Oasis Rubber Stamps |
| 38 | 11 | Batman and Friends Action Figure Toys (4" - 5") |
| 39 | 12 | Delta Dawn Porcelain Doll |
+----+-------------+------------------------------------------------------+
Expected result is similar to amazon's search bar, where you type a keyword and results show after you've entered. What I want in my site is that if I type orchestra in the search field, it will display Boston Symphony Orchestra's row. Or if I type cars, it will display the tonka row.
What I get is that it only shows the vinyls table and never the products table even if I type cars and there should have been a hit in the products table. Or I get an error because union is applicable only to tables with similar number of columns.

You have 2 problems in your code.
First, your count query should work and throw no error, but you only look at the first result row (the one of your vinyls). But your query returns two rows. (It will return one row with the numrows field for the first unioned query (vinyls) and one row with the numrows field for the second query (products) in your union.
You could wrap it into a SUM to only get one result row back:
$stmt = $conn->prepare("SELECT SUM(numrows) AS numrows FROM
(SELECT COUNT(*) AS numrows
FROM vinyls
WHERE ( album_name LIKE :keyword
OR artist_name LIKE :keyword)
)
union
(SELECT COUNT(*) AS numrows
FROM products
WHERE name LIKE :keyword)) AS sumQuery");
The second problem is the query, which loads your result and throws the exception because of the nonmatching column count. You have to define which columns of each of the two unioned queries should be returned, and they have to match on both. So f.e. do this:
$stmt = $conn->prepare("(SELECT id, album_name AS name, artist_name
FROM vinyls
WHERE ( album_name LIKE :keyword
OR artist_name LIKE :keyword)
)
union
(SELECT id, name, NULL AS artist_name
FROM products
WHERE name LIKE :keyword)");

Related

SQL PHP query multiple tables data

Noob alert here!
I'm trying to display some data on my php page from two tables but I don't know how to do it
db_bandas TABLE
banda_id | banda_name
1 | Rolling Beatles
2 | Linkin Bizkit
db_albuns TABLE
album_id | album_name | banda_id | album_ano
1 | Music | 1 | 2000
2 | another | 2 | 2014
3 | good one | 1 | 2004
What I want to show on the page is like :
1 | Music | Rolling Beatles | 2000
2 | another | Linkin Bizkit | 2014
3 | good one | Rolling Beatles | 2004
I've tried the query like this
$sql = "SELECT db_albuns.album_nome AS album_nome, db_albuns.album_id AS album_id, db_albuns.album_ano AS album.ano, db_banda.banda_nome AS banda_nome FROM db_albuns,db_banda";
You can use Join to do it
$sql = "SELECT db_albuns.album_nome,
db_albuns.album_id, db_albuns.album_ano,
db_banda.banda_nome
FROM db_albuns join db_banda
on db_albuns.banda_id = db_banda.banda_id";
Your table name should be albums and columns name should be id, name, banda_id, ano. Your table name is albums, so I think there is no need to use prefix 'albums' for each column.
As well as another table name should be bandas and columns name should be id, name
And your query should be like this
$sql = "SELECT albums.id, albums.name, bandas.name, albums.ano
FROM albums JOIN bandas
on albums.banda_id = bandas.id";
There are a few different ways you can do this, but my go-to is a LEFT JOIN.
$sql = "SELECT album_id, album_name, banda_name, album_ano
FROM albuns LEFT JOIN bandas
USING (banda_id)
WHERE 1=1;
On a side not, prefixing your tables with db_ is a little confusing, because that's the name of a table, not a database.

Single Query to fetch records from multiple tables with different columns

I have 2 different tables as want to get records in a single query. Currently, I am using 2 queries then merging the array result and then displaying the record. Following is my current code:
$db = JFactory::getDbo();
$query1 = "SELECT a.id as cId, a.title, a.parent_id,a.level FROM `categories` AS a WHERE ( a.title LIKE '%keyword%' )";
$result1 = $db->setQuery($query1)->loadObjectlist(); //gives selected records
$query2 = "SELECT b.id as indId, b.indicator , b.cat_id, b.subcat_id, b.section_id FROM `indicator` as b WHERE ( b.indicator LIKE '%keyword%' )";
$result2 = $db->setQuery($query2)->loadObjectlist(); //gives selected records
$_items = array_merge($result1,$result2); //then using $_items in php code to display the data
It is in Joomla however I just want to know how we can merge these 2 queries into one. I tried the following but it gives the result of first query from categories table.
(SELECT id as cId, title, parent_id,level, NULL FROM `categories` WHERE ( title LIKE '%birth%' ))
UNION ALL
(SELECT id as indId, indicator , cat_id, subcat_id, section_id FROM `indicator` WHERE ( indicator LIKE '%birth%' ))
Desired output:
+------+-------------+------------+--------+--------+----------------+--------+-----------+----------+
| cId | title | parent_id | level | indId | indicator | cat_id | subcat_id | section_id
+------+-------------+------------+--------+--------+----------------+--------+-----------+----------+
| 2874 | births | 2703 | 2 | null | null | null | null | null |
+------+-------------+------------+--------+--------+----------------+--------+-----------+----------+
| 13 | birth weight| 12 | 3 | null | null | null | null | null |
+------+-------------+------------+--------+--------+----------------+--------+-----------+----------+
| null | null | null | null | 135 | resident births| 23 | 25 | 1 |
+------+-------------+------------+--------+--------+----------------+--------+-----------+----------+
| null | null | null | null | 189 | births summary | 23 | 25 | 1 |
+------+-------------+------------+--------+--------+----------------+--------+-----------+----------+
This above output will help to get proper pagination records. I tried to use join but JOIN needs a common column in ON clause. Here, I want all the columns and their values. Basically I want to combine the 2 table records in one query. Any help would be appreciated
Here is an example,
There are a number of ways to do this, depending on what you really want. With no common columns, you need to decide whether you want to introduce a common column or get the product.
Let's say you have the two tables:
parts: custs:
+----+----------+ +-----+------+
| id | desc | | id | name |
+----+----------+ +-----+------+
| 1 | Sprocket | | 100 | Bob |
| 2 | Flange | | 101 | Paul |
+----+----------+ +-----+------+
Forget the actual columns since you'd most likely have a customer/order/part relationship in this case; I've just used those columns to illustrate the ways to do it.
A cartesian product will match every row in the first table with every row in the second:
> select * from parts, custs;
id desc id name
-- ---- --- ----
1 Sprocket 101 Bob
1 Sprocket 102 Paul
2 Flange 101 Bob
2 Flange 102 Paul
That's probably not what you want since 1000 parts and 100 customers would result in 100,000 rows with lots of duplicated information.
Alternatively, you can use a union to just output the data, though not side-by-side (you'll need to make sure column types are compatible between the two selects, either by making the table columns compatible or coercing them in the select):
> select id as pid, desc, '' as cid, '' as name from parts
union
select '' as pid, '' as desc, id as cid, name from custs;
pid desc cid name
--- ---- --- ----
101 Bob
102 Paul
1 Sprocket
2 Flange
In some databases, you can use a rowid/rownum column or pseudo-column to match records side-by-side, such as:
id desc id name
-- ---- --- ----
1 Sprocket 101 Bob
2 Flange 101 Bob
The code would be something like:
select a.id, a.desc, b.id, b.name
from parts a, custs b
where a.rownum = b.rownum;
It's still like a cartesian product but the where clause limits how the rows are combined to form the results (so not a cartesian product at all, really).
I haven't tested that SQL for this since it's one of the limitations of my DBMS of choice, and rightly so, I don't believe it's ever needed in a properly thought-out schema. Since SQL doesn't guarantee the order in which it produces data, the matching can change every time you do the query unless you have a specific relationship or order by clause.
I think the ideal thing to do would be to add a column to both tables specifying what the relationship is. If there's no real relationship, then you probably have no business in trying to put them side-by-side with SQL.
As #Sinto suggested the answer for union and dummy column names following is the whole correct query:
(SELECT id as cId, title, parent_id,level, NULL as indId, NULL as indicator , NULL as cat_id, NULL as subcat_id, NULL as section_id FROM `jm_categories` WHERE ( title LIKE '%births%' )) UNION ALL (SELECT NULL as cId, NULL as title, NULL as parent_id,NULL as level, id as indId, indicator , cat_id, subcat_id, section_id FROM `jm_indicator_setup` WHERE ( indicator LIKE '%births%' ))
We have to match the column names from both tables so that we get records as a combination.

How to count the number of grouped rows in mysql when I already count the total rows

I have the below table and I want to do the following:
Count the number of times each item appears in the table
Count the DISTINCT number of items
Group the items by name
+-------+---------+
| id | names |
+-------+---------+
| 1 | Apple |
| 2 | Orange |
| 3 | Grape |
| 4 | Apple |
| 5 | Apple |
| 6 | Orange |
| 7 | Apple |
| 8 | Grape |
+-------+---------+
For the 1. and 3. points I have the following query which works quite well:
SELECT * ,
COUNT(names) as count_name,
FROM tbl_products WHERE type = '1'
GROUP BY names
So I get:
Apple (4)
Orange (2)
Grape (2)
Now I want to also count the number of grouped by rows and added a line to count the distinct elements, however there is some problem, since MySQL accepts the query but cannot output a result:
SELECT * ,
COUNT(names) as count_name,
COUNT(DISTINCT names) as count_total
FROM tbl_products WHERE type = '1'
GROUP BY names
Can anyone advice what might be the problem?
EDIT: For more clearance I want to get a table like this:
+-------+---------+------------+-------------+
| id | names | count_ctg | count_total |
+-------+---------+------------+-------------+
| 1 | Apple | 4 | 3 |
| 2 | Orange | 2 | 3 |
| 3 | Grape | 2 | 3 |
+-------+---------+------------+-------------+
Why not just use the query you are using:
SELECT * ,
COUNT(names) as count_name,
FROM tbl_products WHERE type = '1'
GROUP BY names
This query achieves all three objectives.
1) You get a count of the number of each name value in count_name.
2) The number of distinct names values will be equal to the number of rows in the result set , since you are grouping by names. Pretty much any client-side MySQL DB connection library will enable you to retrieve this value.
3) You meet your third criteria of grouping by name by explictly using GROUP BY names
Of course the value for id in the result set is meaningless, you may want to only select names and count_names.
1-.Count the number of times each item appears in the table:
SELECT names, count(names) FROM tbl_products WHERE type = '1' group by names
2-. How many distinct items exist in the table:
SELECT DISTINCT names FROM tbl_products WHERE type = '1'
3-. Group the items by name:
SELECT count(DISTINCT names) as Total FROM tbl_products WHERE type = '1'
As your last EDIT (ALL IN ONE):
SELECT id, names, count(names), total FROM tbl_products, (select count(distinct names) as total from tbl_products) as total WHERE type = '1' group by names
You can get the count of distinct names in a subquery, then OUTER JOIN that thing back into your main query where you already solved for 1 and 3:
SELECT names ,
COUNT(names) as count_name,
Total
FROM tbl_products
OUTER JOIN (SELECT count(DISTINCT names) as Total FROM tbl_products) t2
WHERE type = '1'
GROUP BY names
You can use the SQL Windowing OVER()
This query returns the row_number() function as the id column in the results, and the over(...) for row_number requires an order by clause. You could order by whatever you want, but it most be ordered by something.
;WITH vwGroups (name, Quantity) AS
(
SELECT name
, COUNT(*)
FROM tbl_products
GROUP BY name
)
SELECT ROW_NUMBER() OVER(ORDER BY Quantity DESC, name) AS id
, name
, Quantity AS count_name
, COUNT(*) OVER () AS count_total
FROM vwGroups

Get matching column & its value MySQL

Here is my query:
SELECT ID, Name, Description
FROM MyTable
WHERE NAME LIKE :search OR Description LIKE '%search%'
OR Status LIKE '%search%'
OR ... Many Other Columns LIKE '%search%
When I get the data I display it to the user that they searched for. Now I only display ID, Name & Description. I also want to display which column matched the searching string. If it matched Status I want to display the status of the row, if it matched any other column I want to display the value of that column that it matched.
Does not matter if it matches multiple columns. I only need the first one.
Is this possible without selecting all columns & then running PHP search on each to find out which one matched? Can it be done simply in MySQL?
I want to make it as fast as possible since my search is becoming pretty slow the more columns & more tables I add to the search and do not want this feature to add a lot of overhead.
Consider the following...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, a INT NOT NULL, b INT NOT NULL, c INT NOT NULL);
INSERT INTO my_table (a,b,c) VALUES
(101,102,103),
(102,103,104),
(103,104,105),
(104,105,106),
(105,106,107);
SELECT * FROM my_table;
+----+-----+-----+-----+
| id | a | b | c |
+----+-----+-----+-----+
| 1 | 101 | 102 | 103 |
| 2 | 102 | 103 | 104 |
| 3 | 103 | 104 | 105 |
| 4 | 104 | 105 | 106 |
| 5 | 105 | 106 | 107 |
+----+-----+-----+-----+
SELECT id
, CASE WHEN a LIKE '%04%' THEN 'a' WHEN b LIKE '%04%' THEN 'b' WHEN c LIKE '%04%' THEN 'c' END col
, CASE WHEN a LIKE '%04%' THEN a WHEN b LIKE '%04%' THEN b WHEN c LIKE '%04%' THEN c END val
FROM my_table;
+----+------+------+
| id | col | val |
+----+------+------+
| 1 | NULL | NULL |
| 2 | c | 104 |
| 3 | b | 104 |
| 4 | a | 104 |
| 5 | NULL | NULL |
+----+------+------+
You can do it a bit messily like this:-
SELECT ID,
Name,
Description,
CASE
WHEN NAME LIKE :search THEN 'NAME'
WHEN Description LIKE :search THEN 'Description'
WHEN Status LIKE :search THEN 'Status'
... Many Other Columns LIKE :search
ELSE 'Unknown Column'
END AS MatchingColumn
FROM MyTable
WHERE NAME LIKE :search OR Description LIKE :search OR Status LIKE :search OR
... Many Other Columns LIKE :search
Down side is that you are repeating the (fairly time consuming) checks. Not sure if MySQL manages to optimise these away, but it wouldn't surprise me if not.
Alternative might be do a UNION of queries, one for each condition. Then use this as a sub query to select the max from.
Using UNION would give you something like this. It does have the benefit that each UNIONed query can then use indexes (your current query can't as you are checking different columns ORed together to get a match), assuming the LIKE doesn't use a leading wildcard.
SELECT ID, Name, Description, MAX(MatchingColumn)
FROM
(
SELECT ID, Name, Description, 'NAME' AS MatchingColumn
FROM MyTable
WHERE NAME LIKE :search
UNION
SELECT ID, Name, Description, 'Description' AS MatchingColumn
FROM MyTable
WHERE Description LIKE :search
UNION
SELECT ID, Name, Description, 'Status' AS MatchingColumn
FROM MyTable
WHERE Status LIKE :search OR
) sub0
GROUP BY ID, Name, Description

Mysql one row left join

I have two tables :
product_list :
id_list | name_product | price |
1 | test01 | 20 |
10 | test02 | 50 |
people :
people_id | people_use_product | people_list
35 | test01 | 1
36 | test02 | 1
They have access to the list id 1 and 10. But the same product can be in both row. ( because the list can be used for many list people ).
I have to get only one row. ( = the price of the product )
SELECT * FROM people
INNER JOIN product_list ON (id_list=1 or id_list=10)
WHERE people_list = 1
LIMIT 0,100
How can I have only one row ?
Your request is wrong. You did not specified an adequate join condition. Your JOIN is actually making a cartesian product of the two tables, that's all.
If people.people_list is your foreign key to product_list :
SELECT * FROM people
INNER JOIN product_list ON (id_list = people_list) -- valid JOIN condition between two tables
WHERE ids_list = 1
may be closer to your needs.
However, I don't really understand your data structure and your real needs so you'll have to refine the query a bit, I guess...

Categories