I have a doubt on how variables work in mysql. As I read in their web looks like setting a variable will become visible to the next row.
My table is like:
A B C N
1 NULL NULL 4
1 NULL NULL 4
1 1 NULL 4
1 1 NULL 4
1 1 1 4
1 1 1 4
What I want is to return only the rows with C = 1. If no rows then return B = 1 and C is NULL if no rows A = 1 and B is NULL and C is NULL.
My idea was:
select N as number,
#var_c := case when (C = 1) then 1 else -1 end as myc,
#var_b := case when (#var_c < 0 and B = 1) then 1 else -1 end as myB,
#var_c := case when (#var_a < 0 and var_b < 0 and C = 1) then 1 else -1 end as myC
from (select #var_a := -1) r_a,
(select #var_b := -1) r_b,
(select #var_c := -1) r_c,
(select A, B, C, N from my_table order by A desc, B desc, C desc) rows
It should (I want to) return
number myA myB myC
4 -1 -1 1
4 -1 -1 1
4 -1 -1 -1
4 -1 -1 -1
4 -1 -1 -1
4 -1 -1 -1
With this and having myA > 0 or myB > 0 or myC > 0 would work.
But it is returning
number myA myB myC
4 1 -1 -1
4 1 -1 -1
4 -1 -1 1
4 -1 -1 1
4 -1 1 -1
4 -1 1 -1
Shouldn't Mysql keep the vars across the rows?
Regards.
First of all, do not use variables like this. You shouldn't expect that you can set a variable in a FROM clause and then access the value in the SELECT clause. That's not how variables work. Normally you should only use a variable in one place in the query, otherwise you can get undeterministic results.
Second of all, why don't you just issue three different queries? First for A = 1 and B is NULL and C is NULL, and if it doesn't return any rows, issue a query with the second condition set. And so forth.
And if you ultimately want to issue just a single query, you can try this:
SELECT N as number
FROM my_table
WHERE IF(EXISTS (SELECT A FROM my_table WHERE A = 1 and B is NULL and C is NULL),
A = 1 and B is NULL and C is NULL,
IF(EXISTS (SELECT A FROM my_table WHERE B = 1 and C is NULL), B = 1 and C is NULL, C = 1))
But it's very likely to kill performance. So better just use three queries instead of one.
UPD: There's another (yet similar) approach:
SELECT N as number
FROM my_table
WHERE (
A = 1
AND B is NULL
AND C is NULL
) OR (
B = 1
AND C is NULL
AND NOT EXISTS (SELECT A FROM my_table WHERE A = 1 and B is NULL and C is NULL)
) OR (
C = 1
AND NOT EXISTS (SELECT A FROM my_table WHERE A = 1 and B is NULL and C is NULL)
AND NOT EXISTS (SELECT A FROM my_table WHERE B = 1 and C is NULL)
)
From the other sample and it's comments, I would approach your query this way. The first query to pre-qualify if there are ANY "C = 1" values and store into a CCnt field. If none found, force the CCnt value to NULL. SECOND, do the same against the "B" column. Since each of these has a limit of 1, should be fast as long as it can FIND an entry of C=1 or B=1 respectively. if not, their values are NULL. Why do this way? The query is done ONCE per run of the query and the answer is done. It doesn't have to keep doing the query repeatedly as can be KILLER performance when used as sub-queries. Doing a count() with no group by will GUARANTEE as single row, so you won't have any Cartesian result that could result in duplicates.
Now that this preemptive querying is done, now add in your main table. At this point, the "HasCEntry.CCnt" value will either be a 1 or NULL.
"HasBEntry.CCnt" value will either be a 1 or NULL.
select
YT.*
from
( select IF( count(*) > 0, 1, NULL ) as CCnt
from YourTable
where C = 1
limit 1 ) HasCEntry,
( select IF( count(*) > 0, 1, NULL ) as BCnt
from YourTable
where B = 1
limit 1 ) HasBEntry,
YourTable YT
where
YT.C = 1
OR ( YT.B = 1 AND HasCEntry.CCnt IS NULL )
OR ( YT.A = 1 AND HasBEntry.BCnt IS NULL AND HasCEntry.CCnt IS NULL )
Now, following the WHERE clause. If there is even 1 record where C=1, then the
YT.C = 1 is all you need and its done... and the other "OR" conditions you don't care about, the record is coming along.
Now, say there are NO entries for C = 1. We know the "CCnt" value is NULL, so that will come into the first "OR" condition and testing against "B"... B Specifically = 1 AND the CCnt IS NULL... So, if there was an entry for "C=2" (3, 4 or any other value), we don't want it. We only want B = 1 AND C = NULL...
If C and B fail, then we fall into the "A" test. Likewise, A = 1 AND B is null AND C is NULL for same reasons as B above.
For a small table, you might not notice any significant performance difference from Shedal's answer. However, it COULD be a huge hit as your table grows.
Related
I have a table like this in MySQL :
Group Seqno Event
1 1 A
1 2 B
1 3 C
1 4 B
1 5 E
1 6 B
1 7 D
1 8 A
I want to count all the rows from last (most recent entry) for each Group with Event = B, and return all remaining rows as soon as it hit count of 2.
The output will be
Group Seqno Event
1 4 B
1 5 E
1 6 B
1 7 D
1 8 A
Any idea how to achieve it.
You seem to want all rows from the second to last "B"?
If so, you can use a correlated subquery:
select t.*
from t
where t.seqno >= (select t2.seqno
from t t2
where t2.group = t.group and t2.event = 'B'
order by t2.seqnum desc
limit 1, 1
);
To handle the case where there may be no "second" sequence number, you can use coalesce():
select t.*
from t
where t.seqno >= coalesce( (select t2.seqno
from t t2
where t2.group = t.group and t2.event = 'B'
order by t2.seqnum desc
limit 1, 1
), t.seqno
);
I want to get all the data from the table, and generate on the fly a column with the value 1 at the parent of the entries with this parent_id. And it should be compatible with "sql server", "postgres", "mysql".
id,parent_id,lft,rgt,level,title,alias,access,path,checked_out
1 0 0 7 0 root root 0 root ...
2 1 1 2 1 ...
Result should be:
id,parent_id,lft,rgt,level,**button**,title,alias,access,path,checked_out
1 0 0 7 0 1 root root 0 root 0...
2 1 1 2 1 **0** ...
Is this ever possible, I´ll never done such a complicated query before.
It should select all table entries, generate a new button column with value 1 or 0, and this only at the parent level of it's entries
Something like this but with a result of all entries:
SELECT a.id,b.id,a.level,a.level+1 as button
FROM `categories` a
JOIN `categories` b ON a.id=b.parent_id
WHERE a.id < b.id AND a.level+1 = b.level AND b.id-a.id=1;
CASE and EXISTS should work in all of these RDBMS:
SELECT id, parent_id, lft, rgt, level
, CASE WHEN EXISTS (
SELECT 1 FROM tbl AS b
WHERE b.parent_id = a.id
AND b.level = a.level + 1
-- AND b.id > a.id -- this one is redundant
AND b.id = a.id + 1)
THEN 1 ELSE 0 END AS button
, ...
FROM tbl AS a;
This returns rows that have any children with button = 1. Else button = 0.
I have an issue with a SQl Query. It's as below.
In a particular field the record consists of "A" and "B" only.
Now if I want to find 2 records of "A" followed by 2 records of "B" and then again 2 Records of "A" and 2 records of "B" and so on till the end of records.
example output should be something like below.
ID Field
--------- -----
2 A
3 A
1 B
5 B
4 A
7 A
6 B
8 B
.........
.........
.............. and so on
Kindly help me with the above....as am stuck for this query.
Thanks!
You can do this by enumerating the rows for each value and then cleverly ordering the results:
select id, field
from (select t.*,
if(field = 'A', #a_rn := #a_rn + 1, #b_rn := #b_rn + 1) as rn
from table t
(select #a_rn := 0, #b_rn := 0) vars
) t
order by (rn - 1) div 2, field;
or slower but cooler...
SELECT x.*
FROM my_table x
JOIN my_table y
ON y.field = x.field
AND y.id <= x.id
GROUP
BY field
, id
ORDER
BY CEILING(COUNT(*)/2)
, field;
In mysql, I have the following table 'Log':
id_user cod info
1 A random_info1
1 A random_info1
1 A random_info1
1 A random_info2
1 B random_info2
1 B random_info2
1 B random_info3
1 B random_info3
2 A random_info4
2 A random_info4
2 B random_info5
2 B random_info5
2 B random_info5
2 B random_info6
With this query:
SELECT
id_user,
SUM(cod = 'A') as A,
SUM(cod = 'B') as B
FROM
Log
GROUP BY
id_user
I get the quantity that each 'cod' appears for each 'id_user:
id_user A B
1 4 4
2 2 4
Now, plus the previous result, I need the number of times a value in 'info' that we've already seen appears again, for each 'cod', like:
id_user A B A_repeated B_repeated
1 4 4 2 2
2 2 4 1 2
A_repeated of id_user = '1' is 2 because random_info1 appears twice after its first appearance.
B_repeated of id_user = '1' is 2 because random_info2 and random_info3 appear once (each) their first appearance.
What changes I need to do in my query to get these results?
Here you go :)
SELECT
id_user,
SUM(cod = 'A') as A,
SUM(cod = 'B') as B,
SUM(cod = 'A' && is_repeating) AS A_repeating,
SUM(cod = 'B' && is_repeating) AS B_repeating
FROM
(
select
l.*
, #is_repeating := if(#prev_id_user = id_user && #prev_cod = cod && #prev_info = info, 1, 0) as is_repeating
, #prev_id_user := id_user
, #prev_cod := cod
, #prev_info := info
from
Log l
, (select #prev_id_user:=null, #prev_cod:=null, #prev_info:=null, #is_repeating:=0) var_init
order by id_user, cod, id
) Log
GROUP BY
id_user
see it working live in an sqlfiddle
I need to add though, that I added an auto_increment column which I used in the order by (which is important!). Like I said already in my comment above there is no order in a database table unless you specify it. You can not rely on the same order from a select without an order by clause, even when it looks like you can do.
UPDATE:
It seems I misunderstood you a little. Here's a corrected version with your desired result of 3 for A_repeating:
SELECT
id_user,
SUM(cod = 'A') as A,
SUM(cod = 'B') as B,
SUM(cod = 'A' && is_repeating) AS A_repeating,
SUM(cod = 'B' && is_repeating) AS B_repeating
FROM
(
select
l.*
, #is_repeating := if(#prev_id_user = id_user && #prev_cod = cod && #prev_info = info, 1, 0) as is_repeating
, #prev_id_user := id_user
, #prev_cod := cod
, #prev_info := info
from
Log l
, (select #prev_id_user:=null, #prev_cod:=null, #prev_info:=null, #is_repeating:=0) var_init
order by id_user, cod, info
) Log
GROUP BY
id_user
I have the following problem:
Scenario:
I've recomposed the table structure to indicate the problem and to hide the irrelevant details.
Let's say I have a table of items.
Item: item_id | buyer_id | unit_id | status_id
All the columns have a not null value referencing to corresponding table.
Other table that I have is a price-catalog with the following structure:
Price: price_id | buyer_id | unit_id | status_id | price
Here any of the referencing values may have the value "-1" meaning "All". So we could have the following rows in price table:
| price_id | buyer_id | unit_id | status_id | price |
| 1 | -1 | 1 | 2 | 15 |
| 2 | 1 | 1 | 2 | 25 |
| 3 | -1 | -1 | 3 | 13 |
Here price 1 would match items with any buyer_id and unit_id = 1, status_id = 2 and price 2 would math items with buyer_id = 1, unit_id = 1 and status_id = 2. Price 3 would match items with any buyer_id, any unit_id and status_id = 3.
If there are multiple prices matching an item the selection is done with the following logic: Take the price that has the most fields specified (smallest amount of -1 values). If there are multiple prices with the same amount of -1 values then we pick the one that has a value different from -1 in the buyer_id. If such doesn't exist we pick the one with "not -1" value at unit_id and last the one with "not -1" value at status.
We can assume that there are not multiple prices matching the exact same group of prices (all prices have a unique combination of buyer_id, unit_id and status_id.)
Problem:
Now I need to make query that selects the right price for each item in item-table. So far i got this:
SELECT item_id, price
FROM item
INNER JOIN price ON 1=1
AND (price.buyer_id = item.buyer_id OR price.buyer_id = -1)
AND (price.unit_id = item.unit_id OR price.unit_id = -1)
AND (price.status_id = item.status_id OR price.status_id = -1)
Basically this will get all the matching prices. So if we have item with buyer_id = 1, unit_id = 1 and status_id = 2 we would get two rows:
| item_id | price |
| 1 | 15 |
| 1 | 25 |
My first idea was to use GROUP BY, but I haven't figured out how to do it so that the price selected is the right one and not just a random/first(?) one.
EDIT: Tried to order the rows (ORDER BY buyer_id DESC, unit_id DESC, status_id DESC) and then group the results based on the ordered sub-query but it seems that the order by doesn't work that way.
So how can I select the right row in GROUP BY? Or what would be an alternative solution to get right prices with a single query (sub-queries are fine)?
Altering the table structure is not really an option in this particular case.
Update
I've been using the solution I submitted earlier but the amount of criteria that the price is selected by has gone up from three to five. This means that I currently have a list of 32 different combinations in my CASE WHEN structure. In case I would need to add another one the list will double again, so I'm still looking for a better solution.
For now the problem was solved as follows. It does the job and at least in the specific case perform fast enough, but I would hope a better solution exists. For now this is what does the trick.
Because I have quite a small group of possible combinations it is relatively easy to form a CASE clause to give numeric value for each combination to represent the order of the different combinations. Next we'll pick for each item_id the row that has the smallest selector value. This is done by Joining with simple group-identifier, max-value-in-group Sub-query. Here is a query to demonstrate the idea:
SELECT item_id, price
FROM (
SELECT item_id, price,
CASE
WHEN price.buyer_id <> -1 AND price.unit_id <> -1 AND price.status_id <> -1 THEN 1
WHEN price.buyer_id <> -1 AND price.unit_id <> -1 AND price.status_id = -1 THEN 2
WHEN price.buyer_id <> -1 AND price.unit_id = -1 AND price.status_id <> -1 THEN 3
WHEN price.buyer_id <> -1 AND price.unit_id = -1 AND price.status_id = -1 THEN 4
WHEN price.buyer_id = -1 AND price.unit_id <> -1 AND price.status_id <> -1 THEN 5
WHEN price.buyer_id = -1 AND price.unit_id <> -1 AND price.status_id = -1 THEN 6
WHEN price.buyer_id = -1 AND price.unit_id = -1 AND price.status_id <> -1 THEN 7
WHEN price.buyer_id = -1 AND price.unit_id = -1 AND price.status_id = -1 THEN 8
END AS selector
FROM item
INNER JOIN price ON 1=1
AND (price.buyer_id = item.buyer_id OR price.buyer_id = -1)
AND (price.unit_id = item.unit_id OR price.unit_id = -1)
AND (price.status_id = item.status_id OR price.status_id = -1)
) AS all_possible_prices
INNER JOIN (
SELECT item_id, MIN(selector)
FROM (
SELECT item_id
CASE
WHEN price.buyer_id <> -1 AND price.unit_id <> -1 AND price.status_id <> -1 THEN 1
WHEN price.buyer_id <> -1 AND price.unit_id <> -1 AND price.status_id = -1 THEN 2
WHEN price.buyer_id <> -1 AND price.unit_id = -1 AND price.status_id <> -1 THEN 3
WHEN price.buyer_id <> -1 AND price.unit_id = -1 AND price.status_id = -1 THEN 4
WHEN price.buyer_id = -1 AND price.unit_id <> -1 AND price.status_id <> -1 THEN 5
WHEN price.buyer_id = -1 AND price.unit_id <> -1 AND price.status_id = -1 THEN 6
WHEN price.buyer_id = -1 AND price.unit_id = -1 AND price.status_id <> -1 THEN 7
WHEN price.buyer_id = -1 AND price.unit_id = -1 AND price.status_id = -1 THEN 8
END AS selector
FROM item
INNER JOIN price ON 1=1
AND (price.buyer_id = item.buyer_id OR price.buyer_id = -1)
AND (price.unit_id = item.unit_id OR price.unit_id = -1)
AND (price.status_id = item.status_id OR price.status_id = -1) ) AS min_seeds
GROUP BY item_id
) AS min_selectors ON all_possible_prices.item_id = min_selectors.item_id
AND all_possible_prices.selector = min_selectors.selector