MYSQL UNION across 3 tables with ORDER BY - php

We have this statement:
(SELECT res_bev.bev_id, property_value.name AS priority
FROM res_bev, bev_property, property_value
WHERE res_bev.res_id='$resIn'
AND bev_property.bev_id=res_bev.bev_id
AND bev_property.type_id='23'
AND property_value.id=bev_property.val_id)
UNION
(SELECT res_bev.bev_id, property_value.name as priority
FROM res_bev, bev_property, property_value
WHERE res_bev.res_id='$resIn'
AND bev_property.bev_id=res_bev.bev_id
AND bev_property.type_id='22'
AND property_value.id=bev_property.val_id)
We have Three Tables:
Res_bev
res_id | bev_id | id
Bev_property
type_id | val_id | bev_id | id
Property_value
name | id
What I am looking for is the results to be ordered by glass price(type_id='23') then bottle price(type_id='22') however it seems the union includes duplicates due to fact the first select returns say 3456 | 7.5 and the second returns 3456 | 55 since the price/Glass is 7.5 and the price/Bottle is 55; how can I eliminate these duplicates form the second SQL statement to return and ordered table?
Also, fooled with creating a pseudo-table via left joins to create a table of bev_id | price/Glass | price/Bottle, however since this should be able to expand to multiple price types I figured a UNION would be more efficient. Just a push in the right direction would be helpful.

You can do it in 1 query by specifying bev_property.type_id to match against an IN() clause with the values inside.
To return only the first one found you should require a DISTINCT SELECT of the accompagnying field bev_id.
To ORDER them just add an appropriate descending ORDER BY clause. This should order first and the filter out the second bev_property.type_id value. (Databases never return anything in a specific order unless you tell them to, some might have an internal convention or it might appear they do but this is never guaranteed to be repeatable unless you specify an ORDER BY clause in your SELECT statement. )
SELECT DISTINCT res_bev.bev_id, property_value.name AS priority
FROM res_bev, bev_property, property_value
WHERE res_bev.res_id='$resIn'
AND bev_property.bev_id=res_bev.bev_id
AND bev_property.type_id IN ('23','22')
AND property_value.id=bev_property.val_id
ORDER BY bev_property.type_id DESC;
A UNION won't really be faster since you'd have to do the whole lookup twice and if you don't have this field indexed then you'll do a whole table traversal with match against 1 element twice as opposed do 1 table traversal that matches against 2 elements. (walking over a whole table is what's generally slow, not matching simple elements against each other)
When properly indexed I think you might have a tiny overhead of executing a new select query and the query analyzer running again but I don't know for sure. It'll probably be smart enough to recognise the similarities between the queries so it won't matter.
It doesn't always hurt to try on specific databases though. Whenever you try query optimisation with different statements use them with EXPLAIN, this will show you what the query will be doing and wether it'll go over whole tables, sort data on file, etc...

Unless I'm missing something, or you have from your question
SELECT res_bev.bev_id,
property_value.name AS priority
FROM res_bev, bev_property, property_value
WHERE res_bev.res_id='$resIn'
AND bev_property.bev_id=res_bev.bev_id
AND (bev_property.type_id='23' OR bev_property.type_id='22')
AND property_value.id=bev_property.val_id)
order by bev_property.type_id desc
PS if you want to order a union
try something along the lines of
Select * from
(
select ...
Union
select ...
) somenameforqryinparentheses
Order by Somecolumn1, somecolumn2

Related

INNER JOIN too slow. how can it bee quicker

My code
SELECT * FROM andmed3 INNER JOIN test ON andmed3.isik like concat('%', test.isik, '%')
In andmed3 i have 130 000 rows and on test i have 10 000 rows, and it wont run.
When i limit it to 0,500 then it will query about 2-3 minutes.
How can it be better?
andmed3 table
id name number isik link stat else
-----------------------------------------------
1 john 15 1233213 none 11 5
8455666
7884555
test table
id isik
-----------
45 8455666
So i need all the rows from the andmed3 where is number what occures in test
The problem is the engine ill need to avalute the LIKE expression for each pair of rows in the join (130.000 X 10.000).
Also indexes are useless in this scenario because the expression need to be evaluated in order to accomplish the join (and you cannot put that expression INSIDE a index)
Maybe it's your architecture/schema the problem. When no one antecipated the need to join two tables based in a string expression.
Possible solution:
(It's a wild guess)
Hard to tell for sure from your example but if andmed3.isik contains all possible values to be used in the join you can try to put that in another table like it:
Andmed3Id isik
--------- -------
1 1233213
1 8455666
1 7884555
Of course to populate this table you ill need a strategy, possbile ones are: in the insert/update, in a batch in some late hour.
If this suits you just need to add one more join in your query.

MySQL SELECT SUM(Column) and SELECT * Cardinality violation: 1241 Operand should contain 1 column(s)

Trying to write statement where in single statement select all (*) and sum one column from the same database and the same table, depending on conditions.
Wrote such statement (based on this Multiple select statements in Single query)
SELECT ( SELECT SUM(Amount) FROM 2_1_journal), ( SELECT * FROM 2_1_journal WHERE TransactionPartnerName = ? )
I understand that SELECT SUM(Amount) FROM 2_1_journal will sum all values in column Amount (not based on codition).
But at first want to understand what is correct statement
With above statement get error SQLSTATE[21000]: Cardinality violation: 1241 Operand should contain 1 column(s)
Can not understand error message. From advice here MySQL - Operand should contain 1 column(s) understand that subquery SELECT * FROM 2_1_journal WHERE TransactionPartnerName = ? must select only one column?
Tried to change statement to this SELECT ( SELECT * FROM 2_1_journal WHERE TransactionPartnerName = ? ), ( SELECT SUM(Amount) FROM 2_1_journal), but get the same error...
What would be correct statement?
SELECT *, (SELECT SUM(Amount) FROM 2_1_journal)
FROM 2_1_journal
WHERE TransactionPartnerName = ?
This selects sums up Amount from the entire table and "appends" all rows where TransactionPartnerName is the parameter you bind in the client code.
If you want to limit the sum to the same criteria as the rows you select, just include it:
SELECT *, (SELECT SUM(Amount) FROM 2_1_journal WHERE TransactionPartnerName = ?)
FROM 2_1_journal
WHERE TransactionPartnerName = ?
A whole different thing: table names like 2_1_journal are strong indicators of a broken database design. If you can redo it, you should look into how to normalize the database properly. It is most likely pay back many times over.
With regard to normalization (added later):
Since the current design uses keys in table names (such as the 2 and 1 in 2_1_journal), I'll quickly illustrate how I think you can vastly improve that design. Lets say that the table 2_1_journal has the following data (I'm just guessing here because the tables haven't been described anywhere yet):
title | posted | content
------+------------+-----------------
Yes! | 2013-01-01 | This is just a test
2nd | 2013-01-02 | Another test
This stuff belongs to user 2 in company 1. But hey! If you look at the rows, the fact that this data belongs to user 2 in company 1 is nowhere to be found.
The problem is that this design violates one of the most basic principles of database design: don't use keys in object (here: table) names. A clear indication that something is very wrong is if you have to create new tables if something new is added. In this case, adding a new user or a new company requires adding new tables.
This issue is easilly fixed. Create one table named journal. Next, use the same columns, but add another two:
company | user | title | posted | content
--------+------+-------+------------+-----------------
1 | 2 | Yes! | 2013-01-01 | This is just a test
1 | 2 | 2nd | 2013-01-02 | Another test
Doing it like this means:
You never add or modify tables unless the application changes.
Doing joins across companies or users (and anything else that used to be part of the table naming scheme is now possible with a single, fairly simple select statement).
Enforcing integrity is easy - if you upgrade the application and want to change the tables, the changes doesn't have to be repeated for each company and user. More importantly, this lowers the risk of having the application get out of sync with the tables in the database (such as adding the field comments to all x_y_journal tables, but forgetting 5313_4324_journal causing the application to break only when user 5313 logs in. This is the kind of problem you don't want to deal with.
I am not writing this because it is a matter of personal taste. Databases are just designed to handle tables that are laid out as I describe above. The design where you use object keys as part of table names has a host of other problems associated with it that are very hard to deal with.

AS or not to AS, queries

I'm fairly new to PHP/MySql and using queries in general. I was just wondering if there's any benefit to using "AS" in a query other than trying to make it look cleaner? Does it speed up the query at all? I probably could have figured this out by a google search but I wanted to ask my first question and see how this works. I WILL select an answer (unlike some people...)
with:
SELECT
news.id as id
news.name as name
FROM news
without:
SELECT
news.id
news.name
FROM news
A more complex example from a many-to-many relationship tutorial I found:
SELECT
c.name,
cf.title
FROM celebrities AS c
JOIN (
SELECT
icf.c_id,
icf.f_id,
f.title
FROM int_cf AS icf
JOIN films AS f
ON icf.f_id = f.f_id
) AS cf
ON c.c_id = cf.c_id
ORDER BY c.c_id ASC
There's no reason to use it if you know there will be no conflicts with other columns. There are no differences in performance, but it does change the name of the output column. It's really useful for when you construct dynamic selects. So for instance, if you had a first_name and a last_name field, you could use the CONCAT function to do:
SELECT CONCAT(first_name, ' ', last_name) AS name FROM whatever
Furthermore, the AS operator works when defining sub-query tables, as you showed in that JOIN. Without that AS, you wouldn't be able to reference that table in the ON clause or the SELECT fields.
1) No, it doesn't affect performance one way or the other
2) As far as readability of the SQL Query, it's just a matter of preference
3) It does affect the readability of the output (it changes the column headers)
Only with regards to ambiguity & reserved words. It does not affect performance. Ambiguity refers to two columns with the same names coming from separate tables. Other RDBMS's will slap you for this. Reserved words refers to system words that must either be escaped using backticks or aliased.
With subqueries, aliased columns may not be derived past the child level.
Speed of query execution when you use aliases ("AS" keyword) and when you dont use them is the same. Reasons for using them:
it makes it easier to work with databases if you have very long or complex table names or column names:
SELECT c.Construction_ID, c.Name, c.Price
FROM Constructions c LEFT JOIN
Constructions_Orders co ON c.Construction_ID = co.Construction_ID
WHERE c.OrderID='1' AND c.Name='ConstructionName'
However, it changes output columns names, take it into account too (makes it harder for person who is reading code).
Aliases are useful for creating dynamic selects:
mysql> SELECT CONCAT(id, name, work_date) as profile
-> FROM employee_tbl;
+-----------------------------+
| profile |
+-----------------------------+
| 1John2007-01-24 |
| 2Ram2007-05-27 |
| 3Jack2007-05-06 |
| 3Jack2007-04-06 |
| 4Jill2007-04-06 |
| 5Zara2007-06-06 |
| 5Zara2007-02-06 |
+-----------------------------+
7 rows in set (0.00 sec)

Select random row per distinct field value?

I have a MySQL query that results in something like this:
person | some_info
==================
bob | pphsmbf24
bob | rz72nixdy
bob | rbqqarywk
john | kif9adxxn
john | 77tp431p4
john | hx4t0e76j
john | 4yiomqv4i
alex | n25pz8z83
alex | orq9w7c24
alex | beuz1p133
etc...
(This is just a simplified example. In reality there are about 5000 rows in my results).
What I need to do is go through each person in the list (bob, john, alex, etc...) and pull out a row from their set of results. The row I pull out is sort of random but sort of also based on a loose set of conditions. It's not really important to specify the conditions here so I'll just say it's a random row for the example.
Anyways, using PHP, this solution is pretty simple. I make my query and get 5000 rows back and iterate through them pulling out my random row for each person. Easy.
However, I'm wondering if it's possible to get what I would from only a MySQL query so that I don't have to use PHP to iterate through the results and pull out my random rows.
I have a feeling it might involve a BUNCH of subselects, like one for each person, in which case that solution would be more time, resource and bandwidth intensive than my current solution.
Is there a clever query that can accomplish this all in one command?
Here is an SQLFiddle that you can play with.
To get a random value for a distinct name use
SELECT r.name,
(SELECT r1.some_info FROM test AS r1 WHERE r.name=r1.name ORDER BY rand() LIMIT 1) AS 'some_info'
FROM test AS r
GROUP BY r.name ;
Put this query as it stands in your sqlfiddle and it will work
Im using r and r1 as table alias names. This will also use a subquery to select a random some_info for the name
SQL Fiddle is here
My first response would be to use php to generate a random number:
$randId = rand($min, $max);
Then run a SQL query that only gets the record where your index equals $randID.
Here is the solution:
select person, acting from personel where id in (
select lim from
(select count(person) c, min(id) i, cast(rand()*(count(person)-1) +min(id)
as unsigned) lim from personel group by person order by i) t1
)
The table used in the example is below:
create table personel (
id int(11) not null auto_increment,
person char(16),
acting char(19),
primary key(id)
);
insert into personel (person,acting) values
('john','abd'),('john','aabd'),('john','adbd'),('john','abfd'),
('alex','ab2d'),('alex','abd3'),('alex','ab4d'),('alex','a6bd'),
('max','ab2d'),('max','abd3'),('max','ab4d'),('max','a6bd'),
('jimmy','ab2d'),('jimmy','abd3'),('jimmy','ab4d'),('jimmy','a6bd');
You can limit the number of queries, and order by "rand()" to get your desired result.
Perhaps if you tried something like this:
SELECT name, some_info
FROM test
WHERE name = 'tara'
ORDER BY rand()
LIMIT 1

MySQL: How to get a sequential number with rows?

How can I number my results where the lowest ID is #1 and the highest ID is the #numberOfResults
Example: If I have a table with only 3 rows in it. whose ID's are 24, 87, 112 it would pull like this:
ID 24 87 112
Num 1 2 3
The reason why I want this, is my manager wants items to be numbered like item1, item2, etc. I initially made it so it used the ID but he saw them like item24, item87, item112. He didn't like that at all and wants them to be like item1, item2, item3. I personally think this is going to lead to problems because if you are deleting and adding items, then item2 will not always refer to the same thing and may cause confusion for the users. So if anyone has a better idea I would like to hear it.
Thanks.
I agree with the comments about not using a numbering scheme like this if the numbers are going to be used for anything other than a simple ordered display of items with numbers. If the numbers are actually going to be tied to something, then this is a really bad idea!
Use a variable, and increment it in the SELECT statement:
SELECT
id,
(#row:=#row+1) AS row
FROM table,
(SELECT #row:=0) AS row_count;
Example:
CREATE TABLE `table1` (
`id` int(11) NOT NULL auto_increment,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
INSERT INTO table1 VALUES (24), (87), (112);
SELECT
id,
(#row:=#row+1) AS row
FROM table1,
(SELECT #row:=0) AS row_count;
+-----+------+
| id | row |
+-----+------+
| 24 | 1 |
| 87 | 2 |
| 112 | 3 |
+-----+------+
How it works
#row is a user defined variable. It is necessary to set it to zero before the main SELECT statement runs. This can be done like this:
SELECT #row:=0;
or like this:
SET #row:=0
But it is handy to tie the two statements together. This can be done by creating a derived table, which is what happens here:
FROM table,
(SELECT #row:=0) AS row_count;
The the second SELECT actually gets run first. Once that's done, it's just a case of incrementing the value of #row for every row retrieved:
#row:=#row+1
The #row value is incremented every time a row is retrieved. It will always generate a sequential list of numbers, no matter what order the rows are accessed. So it's handy for some things, and dangerous for other things...
Sounds like it would be better just making that number in your code instead of trying to come up with some sort of convoluted way of doing it using SQL. When looping through your elements, just maintain the sequentiality there.
What is the ID being used for?
If it's only for quick and easy reference then that's fine, but if it's to be used for deleting or managing in any way as you mentioned then your only option would be to assign a new ID column that is unique for each row in the table. Doing this is pointless though because that duplicates the purpose of your initial ID column.
My company had a similar challenge on a CMS system that used an order field to sort the articles on the front page of the site. The users wanted a "promote, demote" icon that they could click that would move an article up or down.
Again, not ideal, but the strategy we used was to build a promote function and accompanying demote function that identified the current sort value via query, added or subtracted one from the previous or next value, respectively, then set the value of the initially promoted/demoted item. It was also vital to engineer the record insert to accurately set the initial value of newly added records so inserts wouldn't cause a duplicate value to be added. This was also enforced at the DB level for safety's sake. The user was never allowed to directly key in the value of the sort, only promote or demote via icons. To be honest, it worked quite well for the user.
If you have to go this route.....it's not impossible. But there is brain damage involved....

Categories