Given these tables:
+---------------+---------+
| Field | Type |
+---------------+---------+
| group_id | int(10) |
| subscriber_id | int(10) |
+---------------+---------+
+---------------+--------------+
| Field | Type |
+---------------+--------------+
| subscriber_id | int(10) |
| firstname | varchar(50) |
| lastname | varchar(50) |
| company | varchar(120) |
| position | varchar(50) |
| email | text |
| lettertype | varchar(5) |
| status | varchar(20) |
+---------------+--------------+
I used the following query to get a subset of subscribers:
SELECT *
FROM newsletter_subscribe AS a, newsletter_subscriber AS b
WHERE (a.group_id = 1 or a.group_id = 4)AND (a.subscriber_id = b.subscriber_id)
What I'd like to do is exclude from the subset if a row exists in newsletter_subscribe where group_id = 3 then the newsletter_subscribe from that row is excluded from the result.
My thought was to make a temporary table to replace a, but I'm not certain how to go about it.
SELECT *
FROM newsletter_subscribe AS a, newsletter_subscriber AS b
WHERE (a.group_id = 1 or a.group_id = 4) AND (a.subscriber_id = b.subscriber_id) AND (b.subscriber_id NOT IN (SELECT subscriber_id FROM newsletter_subscribe WHERE group_id = 3))
As it stands now, you'll never get group_id=3, because you only allow groups 1 and 4 in the first term of your where clause. If you want ALL groups EXCEPT 3, then use
WHERE (a.group_id <> 3) AND (a.subscriber_id = b.subscriber_id)
or perhaps
WHERE 3 NOT IN (a.group_id, b.group_id) AND (a.subscriber_id = b.subscriber_id)
to exclude it from both tables.
Related
I have a table that uses CHAR as the primary key for customers. I am attempting to load this table into a schema such that the primary key should be an INT.
DROP TABLE IF EXISTS `customers`;
CREATE TABLE `customers` (
`customer_id` char(5) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `customers` VALUES ('99944'),('99946'),('99976'),('A0014'),('A0049'),('A0124'),('C01AH'),('C01AQ'),('C01AW'),('C01AX'),('C01AY'),('C01AZ');
Fiddle
I have attempted variations on select cast(customer_id AS UNSIGNED) FROM customers; but only get back 0s for the non-int rows. How do I cast the non-int rows into a consistent INT result?
The ideal result would look like this:
For customer IDs that are solely integers, leave them alone.
For customer IDs that contain any letter, replace everything in the ID with a unique numerical identifier.
Expected result:
SELECT * FROM Customers;
`customer_id`
-------
99944
99946
99976
13871911
13871912
13871913
13872128
13872229
13872293
13872505
13872512
13872561
GMB did give me a other idea.
Using the HEX() and CONV(.., 16, 10) to convert from hexadecimals into decimales
Query
SELECT
customers.customer_id
, CASE
WHEN (customers.customer_id >> 0) > 0
THEN customers.customer_id >> 0
ELSE
CONV(HEX(customers.customer_id), 16, 10)
END
AS customer_id_int
FROM
customers;
Result
| customer_id | customer_id_int |
| ----------- | --------------- |
| 99944 | 99944 |
| 99946 | 99946 |
| 99976 | 99976 |
| A0014 | 279981338932 |
| A0049 | 279981339705 |
| A0124 | 279981404724 |
| C01AH | 288571343176 |
| C01AQ | 288571343185 |
| C01AW | 288571343191 |
| C01AX | 288571343192 |
| C01AY | 288571343193 |
| C01AZ | 288571343194 |
p.s
It might be generating a to large int you need to use a BIGINT datatype.
see demo
Updated
A other method to generate smaller int's (UNSIGNED INT) which uses a "SQL number generator", SUBSTRING(), ORD() and GROUP_CONCAT().
Query
SELECT
customers.customer_id
CASE
WHEN customers.customer_id >> 1 > 0
THEN customers.customer_id
ELSE
GROUP_CONCAT(
CASE
WHEN SUBSTRING(customers.customer_id, number_generator.number, 1) NOT BETWEEN 'A' AND 'Z'
THEN SUBSTRING(customers.customer_id, number_generator.number, 1) >> 1
ELSE ORD(SUBSTRING(customers.customer_id, number_generator.number, 1))
END
ORDER BY
number_generator.number ASC
SEPARATOR ''
)
END
) AS customer_id_int
FROM (
SELECT
record_1.number
FROM (
SELECT 1 AS number UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5
) AS record_1
) AS number_generator
CROSS JOIN
customers
GROUP BY
customers.customer_id
ORDER BY
customers.customer_id ASC
Result
| customer_id | customer_id_int |
| ----------- | --------------- |
| 99944 | 99944 |
| 99946 | 99946 |
| 99976 | 99976 |
| A0014 | 650002 |
| A0049 | 650024 |
| A0124 | 650012 |
| C01AH | 67006572 |
| C01AQ | 67006581 |
| C01AW | 67006587 |
| C01AX | 67006588 |
| C01AY | 67006589 |
| C01AZ | 67006590 |
see demo
With Maria DB >= 10.0.5, here is a solution to turn a string primary key to an integer primary key in a predictable manner :
SELECT
customer_id old_id,
CAST(
REGEXP_REPLACE(customer_id, '([^0-9])', ORD('$1'))
AS UNSIGNED
) new_id
FROM customers;
REGEXP_REPLACE() captures non-numeric characters (anywhere in the string) and ORD() turns each of them into its ordinal (numerical) represtation.
Demo on DB Fiddle :
old_id | new_id
:----- | -------:
99944 | 99944
9Z946 | 936946
A9CZ6 | 36936366
A0C14 | 3603614
0ABC0 | 3636360
Using MySQL 8.0 REGEXP_REPLACE:
select cast(REGEXP_REPLACE(customer_id,'[^0-9]','') AS UNSIGNED) FROM customers;
db<>fiddle demo
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 years ago.
Improve this question
Working on a project with students/grades/etc, I need to update the top 3 students every once in a while. I came up with the query below. However, I am having trouble getting their rank/order. I know how to do that in a simple query, but in a more complex one, it is not working.
I am getting all of the other columns correctly, and, with all the methods I tried to get the order by, I sometimes got 0 (like the current state of the code), sometimes values that are just wrong (1, 11, 10), etc.
NOTE: I have checked various questions (including the question below), but I just couldn't figure out how to place them in my query.
What is the best way to generate ranks in MYSQL?
Summary:
GOAL:
- Get sum of each students' marks from marks, divide that on the number of entries in the table (again marks). Students are from a given grade.
- Use sum(mark) to rank these students.
- Get the top three.
- Place the top three students from that grade in the TopStudents table, with their average marks (as sum) and their id's.
TABLES:
Students table contains info about student including id:
+-------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------------+------+-----+---------+----------------+
| id | int (20) unsigned | NO | PRI | NULL | auto_increment |
| name |varchar(20) unsigned | NO | | NULL | |
+-------------+---------------------+------+-----+---------+----------------+
Marks Table has marks of each student on each exam
+-------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------------+------+-----+---------+----------------+
| id |int (20) unsigned | NO | PRI | NULL | auto_increment |
| idStudent |int (20) unsigned | NO | FOR | NULL | |
| mark |tinyInt (3) unsigned | NO | | NULL | |
| idExam |int (20) unsigned | NO | FOR | NULL | |
+-------------+---------------------+------+-----+---------+----------------+
Grade Table has grade id and name:
+-------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------------+------+-----+---------+----------------+
| id | int (20) unsigned | NO | PRI | NULL | auto_increment |
| name |varchar(20) unsigned | NO | | NULL | |
+-------------+---------------------+------+-----+---------+----------------+
Class Table classes for each grade. References table
+-------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------------+------+-----+---------+----------------+
| id | int (20) unsigned | NO | PRI | NULL | auto_increment |
| name |varchar(20) unsigned | NO | | NULL | |
| idGrade | int (20) unsigned | NO | FOR | NULL | |
+-------------+---------------------+------+-----+---------+----------------+
and finally, the infamous TopStudents Table .
+-------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+---------------------+------+-----+---------+----------------+
| id | int (20) unsigned | NO | PRI | NULL | auto_increment |
| idStudent | int (20) unsigned | NO | FOR | NULL | |
| sumMarks | int (20) unsigned | NO | | NULL | |
| rank |tinyInt (1) unsigned | NO | | NULL | |
| date |date unsigned | NO | | NULL | |
+-------------+---------------------+------+-----+---------+----------------+
ATTEMPTS:
Attempt 1: ERROR: all ranks are 0
INSERT INTO topStudents(`date`, idStudent, `sum`, `order`)
SELECT
'2018-10-10' AS DATE,
student.id AS idStudent,
AVG(marks.mark)
#n = #n + 1 AS `order`
FROM
marks
INNER JOIN student ON student.id = marks.idStudent
INNER JOIN class ON class.id = marks.idClass
INNER JOIN grade ON class.idGrade = grade.id
WHERE
grade.id = 2
GROUP BY
marks.idStudent
ORDER BY
SUM(mark)
DESC
LIMIT 3
Attempt 2: ranks returned: 1, 11, 10
SET #n := 0;
INSERT INTO topStudents(`date`, idStudent, `sum`, `rank`)
SELECT
'2018-10-10' AS DATE,
tbl.idStudent AS idStudent,
AVG(tbl.mark) AS `sum`,
rnk AS `rank`
FROM (SELECT student.id AS idStudent, SUM(mark) AS mark FROM
marks
INNER JOIN student ON student.id = marks.idStudent
INNER JOIN class ON class.id = marks.idClass
INNER JOIN grade ON class.idGrade = grade.id
WHERE
grade.id = 2
GROUP BY
marks.idStudent
ORDER BY
SUM(mark)
DESC
LIMIT 3) AS tbl, (SELECT #n = #n + 1) AS rnk
In more recent versions of MySQL, you need to use a derived table for the ordering, before assigning the ranks:
INSERT INTO topStudents (`date`, idStudent, `sum`, `order`)
SELECT date, idStudent, `sum`, (#n := #n + 1) AS `order`
FROM (SELECT '2018-10-10' AS DATE, s.id AS idStudent,
SUM(m.mark) / (SELECT COUNT(*) FROM marks m2 WHERE m2.idStudent = m.idStudent) AS `sum`
FROM marks m JOIN
student s
ON s.id = m.idStudent JOIN
class c
ON c.id = m.idClass JOIN
grade g
ON c.idGrade = g.id
WHERE g.id = 2
GROUP BY m.idStudent
ORDER BY SUM(mark) DESC
LIMIT 3
) sm CROSS JOIN
(SELECT #n := 0) params;
I am almost certain that the calculation for sum is incorrect, and that you really intend avg(mark). However, this is the logic you have in your question.
I'm developing a system to manage rental processes right now and I'm wondering how to efficiently query all rentable objects with the person name, who is currently renting it, if the object is rented at the moment. Otherwise there should be NULL in that column.
My tables look like:
object
| object_id | object_name |
---------------------------
| 1 | Object A |
| 2 | Object B |
| 3 | Object C |
| 4 | Object D |
| 5 | Object E |
---------------------------
person
| person_id | person_name |
---------------------------
| 1 | John Doe |
| 2 | Jane Doe |
| 3 | Max Muster |
| 4 | Foobar |
---------------------------
rental
| rental_id | rental_state| person_person_id |
----------------------------------------------
| 1 | open | 1 |
| 2 | returned | 1 |
| 3 | returned | 2 |
| 4 | open | 3 |
| 5 | returned | 4 |
----------------------------------------------
rental2object
| rental_rental_id | object_object_id |
---------------------------------------
| 1 | 1 |
| 2 | 2 |
| 2 | 3 |
| 3 | 3 |
| 4 | 2 |
| 4 | 5 |
| 5 | 2 |
---------------------------------------
The result I want should look like this:
| object_id | object_name | rented_to |
-------------------------------------------
| 1 | Object A | John Doe |
| 2 | Object B | Max Muster |
| 3 | Object C | NULL |
| 4 | Object D | NULL |
| 5 | Object E | Max Muster |
-------------------------------------------
What I've got so far is:
SELECT `object_id`, `object_name`, `person_name` FROM `object`
LEFT JOIN `rental2object` ON `object_id` = `object_object_id`
LEFT JOIN `rental` ON `rental_id` = `rental_rental_id` AND `rental_state` = 'open'
LEFT JOIN `person` ON `person_id` = `person_person_id`
GROUP BY `object_id`
The obvious problem is that I don't know how to aggregate the right way while grouping.
What would be the most efficient way to achieve my goal? Appreciate your help.
EDIT
Corrected the expected result, so that Object B is also rented to Max Muster.
About your question
Objects #2 and #5 are both in rental #4. But, on your expected results, you are handling both in different way. Object E and Object B both should be the same behaviour because they are in the same rental. If not, you should to explain witch is the criteria to know if a product has or not a related person.
Group by
To be SQL92 compliant you should to include in select clause all nonaggregated columns:
SELECT `object_id`, `object_name`, `person_name` as rented_to
FROM `object`
...
GROUP BY `object_id`, `object_name`, `person_name`
To be SQL99 compliant you should to include in select clause all nonaggregated columns non functionlly dependent, in your case, they are a dependent between object_id and object_name: object_id -> object_name (the field rental_state breaks dependent functionality to person), then you can just to write:
SELECT `object_id`, `object_name`, `person_name` as rented_to
FROM `object`
...
GROUP BY `object_id`, `person_name`
MySQL 5.7.5 and up implements detection of functional dependence, then this last select is valid but I suggest to you that, for readability, use the first one.
Read MySQL Handling of GROUP BY for more info and ONLY_FULL_GROUP_BY parameter details.
Performance
Be sure you have indexes for:
object: Object_id ( is primary key, then index is implicit )
rental2object: object_object_id ( may be a composite index with the other field, but be sure object_object_id is the first field on index )
rental : rental_id & rental_state ( a composite index with both fields )
person: person_id ( is primary key, then index is implicit )
Try this
SELECT
o.object_id,
o.object_name,
p.person_name AS rent_to
FROM
rental2object ro
RIGHT JOIN object o ON ro.object_object_id = o.object_id
LEFT JOIN rental r ON ro.rental_rental_id = r.rental_id AND r.rental_status = 'open'
JOIN person p ON r.person_person_id = p.person_id
SELECT `object_id`, `object_name`,
case
when rental_state = 'Open' then `person_name`
when r1.rental_rental_id is null then null
else `rental_state`
end as RentedTo
FROM `object`
LEFT JOIN `rental2object` r1 ON `object_id` = r1.`object_object_id`
LEFT JOIN `rental` ON `rental_id` = r1.`rental_rental_id`
LEFT JOIN `person` ON `person_id` = `person_person_id`
where r1.rental_rental_id =
(select max(r2.`rental_rental_id`)
from `rental2object` r2
where r2.`object_object_id` = r1.`object_object_id`
group by r2.`object_object_id`)
or r1.rental_rental_id is null
GROUP BY `object_id`;
I have a table:
id | type | subtype
how shall I create a query to output as following
type1 | subtype1 | count-subtype1 | count-type1
type1 | subtype2 | count-subtype2 | count-type1
type2 | subtype3 | count-subtype3 | count-type2
type2 | subtype4 | count-subtype4 | count-type2
Namely subtotal as a column in output.
With no "WITH ROLLUP"
To awnser this query sucessfully (and this is where some awsers fails) is that you need to know what a roollup does. If you don't want to perform a "manual" sql rollup, there is another answer around which solves your query.
what do you need is two queries, one to count the subtypes within the types and another to count the types.
first count the subtypes (and lets call this query s).
select count(*) count_subtype, type, subtype from Foo group by type, subtype;
and another query to count the types (and lets call this query t).
select count(*) count_type, type from Foo froup by type
and now you need to merge the two queries:
select t.type, s.subtype, s.count_subtype, t.conttype from
(select count(*) count_subtype, type, subtype from Foo group by type, subtype) as s
join
(select count(*) count_type, type from Foo froup by type) as t
on (t.type=s.type);
Assuming that I have this structure of table:
CREATE TABLE `test` (
`id` int(11) NOT NULL auto_increment,
`type` varchar(128) default NULL,
`subtype` varchar(128) default NULL,
KEY `id` (`id`));
And this data:
INSERT INTO `test` VALUES (1,'a','1'),(2,'a','2'),(3,'a','3'),(4,'a','4'),(5,'b','4'),
(6,'c','4'),(7,'c','1'),(8,'c','2'),(9,'c','2');
I can do this:
SELECT test.type, test.subtype, count(test.subtype) as countsubtype, testbytype.counttype
FROM (test)
LEFT JOIN (SELECT type, count(type) AS counttype FROM test group by type) AS testbytype ON test.type = testbytype.type
GROUP by type, subtype;
+------+---------+--------------+-----------+
| type | subtype | countsubtype | counttype |
+------+---------+--------------+-----------+
| a | 1 | 1 | 4 |
| a | 2 | 1 | 4 |
| a | 3 | 1 | 4 |
| a | 4 | 1 | 4 |
| b | 4 | 1 | 1 |
| c | 1 | 1 | 4 |
| c | 2 | 2 | 4 |
| c | 4 | 1 | 4 |
+------+---------+--------------+-----------+
Query:
SELECT type, subtype, sum(type), sum(subtype) from table_name GROUP BY id
I am having problems selecting items from a table where a device_id can be either in the from_device_id column or the to_device_id column. I am trying to return all chats where the given device is ID is in the from_device_id or to_device_id columns, but only return the latest message.
select chat.*, (select screen_name from usr where chat.from_device_id=usr.device_id limit 1) as from_screen_name, (select screen_name from usr where chat.to_device_id=usr.device_id limit 1) as to_screen_name from chat where to_device_id="ffffffff-af28-3427-a2bc-83865900edbe" or from_device_id="ffffffff-af28-3427-a2bc-83865900edbe" group by from_device_id, to_device_id;
+----+--------------------------------------+--------------------------------------+---------+---------------------+------------------+----------------+
| id | from_device_id | to_device_id | message | date | from_screen_name | to_screen_name |
+----+--------------------------------------+--------------------------------------+---------+---------------------+------------------+----------------+
| 20 | ffffffff-af28-3427-a2bc-83860033c587 | ffffffff-af28-3427-a2bc-83865900edbe | ee | 2011-02-28 12:36:38 | kevin | handset |
| 1 | ffffffff-af28-3427-a2bc-83865900edbe | ffffffff-af28-3427-a2bc-83860033c587 | yyy | 2011-02-27 17:43:17 | handset | kevin |
+----+--------------------------------------+--------------------------------------+---------+---------------------+------------------+----------------+
2 rows in set (0.00 sec)
As expected, two rows are returned. How can I modify this query to only return one row?
mysql> describe chat;
+----------------+---------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+---------------+------+-----+-------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| from_device_id | varchar(128) | NO | | NULL | |
| to_device_id | varchar(128) | NO | | NULL | |
| message | varchar(2048) | NO | | NULL | |
| date | timestamp | YES | | CURRENT_TIMESTAMP | |
+----------------+---------------+------+-----+-------------------+----------------+
5 rows in set (0.00 sec)
select chat.*,
(select screen_name
from usr
where chat.from_device_id=usr.device_id
limit 1
) as from_screen_name,
(select screen_name
from usr
where chat.to_device_id=usr.device_id
limit 1
) as to_screen_name
from chat
where to_device_id="ffffffff-af28-3427-a2bc-83865900edbe" or
from_device_id="ffffffff-af28-3427-a2bc-83865900edbe"
group by from_device_id, to_device_id
order by date DESC
limit 1;
You need to tell SQL that it should sort the returned data by date to get the most recent chat. Then you just limit the returned rows to 1.
You shouldn't need to use a Group By at all. Rather, you can simply use the Limit predicate to return the last row. In addition, you shouldn't need subqueries as you can use simply Joins. If chat.from_device_id and chat.to_device_id are both not-nullable, then you can replace the Left Joins with Inner Joins.
Select chat.id
, chat.from_device_id
, chat.to_device_id
, chat.message
, chat.date
, FromUser.screen_name As from_screen_nam
, ToUser.screen_name As to_screen_name
From chat
Left Join usr As FromUser
On FromUser.device_id = chat.from_device_id
Left Join usr As ToUser
On ToUser.device_id = chat.to_device_id
Where chat.to_device_id="ffffffff-af28-3427-a2bc-83865900edbe"
Or chat.from_device_id="ffffffff-af28-3427-a2bc-83865900edbe"
Order By chat.date Desc
Limit 1