99% working behavior from mysql statement needs to be 100% - mysql

I have inherrited a DB that I've been tasked to mine for Data.
There are 2 tables that are loosely associated - atm and dslams.
The atm table contains "remotename", "rst", and "CardNumber" fields that relate to the dslams "hostname" field.
The atm table contains the port information for the dslam cards and the dslams table contains the information about the dslam card itself.
I've been tasked with printing out all the locations (dslams.name) that have a certain type of card (dslams.model="6256") and a count of all the ports on that card that have a certain level of service (atm.speed LIKE "RI_%%09" OR atm.speed LIKE "RI%%1%").
I've crafted the following statement which almost works...
SELECT distinct(dslams.name) AS Remote, Count(atm.speed) AS Customers, dslams.model
FROM dslams
LEFT JOIN atm
ON (dslams.hostname = CONCAT(atm.remotename,'-',atm.rst,'-S',atm.CardNumber)) AND (atm.speed LIKE "RI_%_%09" OR atm.speed LIKE "RI_%_%1_%")
GROUP BY dslams.name
HAVING dslams.model="6256"
ORDER BY dslams.name;
This prints out exactly what I need for all but 1 of the locations.
ie.
MariaDB [dsl]> SELECT distinct(dslams.name) AS Remote, Count(atm.speed) AS Customers, dslams.model
-> FROM dslams
-> LEFT JOIN atm
-> ON (dslams.hostname = CONCAT(atm.remotename,'-',atm.rst,'-S',atm.CardNumber)) AND (atm.speed LIKE "RI_%_%09" OR atm.speed LIKE "RI_%_%1_%")
-> GROUP BY dslams.name
-> HAVING dslams.model="6256"
-> ORDER BY dslams.name;
+---------+-----------+-------+
| Remote | Customers | model |
+---------+-----------+-------+
| ANTH-C2 | 1 | 6256 |
| BETY-C2 | 1 | 6256 |
| BHOT-C2 | 6 | 6256 |
| BNSH-C2 | 1 | 6256 |
| BUG2-C2 | 1 | 6256 |
| CCRK-C2 | 0 | 6256 |
...
| STLN-C2 | 1 | 6256 |
| SUMR-C2 | 2 | 6256 |
...
| WGRV-C2 | 0 | 6256 |
+---------+-----------+-------+
63 rows in set (0.34 sec)
For some reason there's one location that's not getting counted - STWL-C2.
MariaDB [dsl]> SELECT distinct(name), model FROM dslams WHERE model="6256" order by name;
+---------+-------+
| name | model |
+---------+-------+
| ANTH-C2 | 6256 |
| BETY-C2 | 6256 |
| BHOT-C2 | 6256 |
| BNSH-C2 | 6256 |
| BUG2-C2 | 6256 |
| CCRK-C2 | 6256 |
...
| STWL-C2 | 6256 |
...
| WGRV-C2 | 6256 |
+---------+-------+
64 rows in set (0.00 sec)
There's no difference in the tables between the STWL-C2 location and the other locations so it should print out with a count of 0.
Can anyone help me figure out why that 1 location is being missed?
Any help or direction would be appreciated as I am a rookie SQL programmer trying to understand this as best I can.
Best Regards,
Joe

Don't use HAVING dslams.model = '6256', put that in the WHERE clause. When you use HAVING, it filters after grouping. When you group by name, the result can contain the model from any row in the group, and it won't necessarily choose model = '6256'.
SELECT dslams.name AS Remote, Count(atm.speed) AS Customers, dslams.model
FROM dslams
LEFT JOIN atm
ON (dslams.hostname = CONCAT(atm.remotename,'-',atm.rst,'-S',atm.CardNumber)) AND (atm.speed LIKE "RI_%_%09" OR atm.speed LIKE "RI_%_%1_%")
WHERE dslams.model = '6256'
GROUP BY dslams.name
ORDER BY dslams.name;

Related

When inserting new record in existing table tt goes up instead of down

I have already created table I want to add extra row when adding extra row the created extra row goes up. I want that row at the bottom.
MariaDB [armydetails]> insert into armydetails values('r05','Shishir','Bhujel','Jhapa','9845678954','male','1978-6-7','1994-1-3','ran5','Na11088905433');
Query OK, 1 row affected (0.17 sec)
MariaDB [armydetails]> select * from armydetails;
+-------+---------+---------+-----------+------------+--------+------------+------------+--------+----------------+
| regNo | fName | lName | address | number | gender | DOB | DOJ | rankID | accountNo |
+-------+---------+---------+-----------+------------+--------+------------+------------+--------+----------------+
| r05 | Shishir | Bhujel | Jhapa | 9845678954 | male | 1978-06-07 | 1994-01-03 | ran5 | Na11088905433 |
| ro1 | Milan | Katwal | Dharan | 9811095122 | Male | 1970-01-03 | 1990-01-01 | ran1 | Na11984567823 |
| ro2 | Hari | Yadav | Kathmandu | 9810756436 | male | 1980-06-07 | 2000-05-06 | ran2 | Na119876678543 |
| ro3 | Khrisna | Neupane | Itahari | 9864578934 | male | 1980-02-02 | 2001-01-07 | ran3 | Na11954437890 |
| ro4 | Lalit | Rai | Damak | 9842376547 | male | 1989-05-09 | 2005-01-02 | ran4 | Na11064553221 |
+-------+---------+---------+-----------+------------+--------+------------+------------+--------+----------------+
5 rows in set (0.00 sec)
MariaDB [armydetails]>
The SQL 2011 publication from ISO/IEC 9075 says:
In general, rows in a table are unordered; however, rows in a table are ordered if the table is the result of a that immediately contains an « order by clause ».
In a SQL database, there is no underlying, default ordering for records. A relational database basically stores a table as a bunch of unordered records.
When records are SELECTed without an ORDER BY clause, they come out in an undefined order, that in no way is guaranteed to be consistent over subsequent queries (including the very same query being executed several times). This is true for MySQL and for other RDBMS.
The only way to properly order records is to use an ORDER BY clause, like:
select * from armydetails order by regNo
Suggested lecture: Tom Kyte Blog : Order in the Court!.
You can simply add an ORDER BY clause to your statment as follows:
SELECT * FROM armydetails ORDER BY regNO DESC;

How to make IN condition check for set values

Basically I have two tables
MY_CHARACTER:
_________________________________________________
| char_ID | char_name | char_class | char_rank |
|------------------------------------------------|
| 1 | Aragorn | Fighter | 99 |
| 2 | Legolas | Archer | 90 |
| 3 | Smeagle | Spy | 20 |
|________________________________________________|
and
EQUIPMENT:
_________________________________________________
| equip_ID | equip_name | equip_owner | required |
|--------------------------------------------------|
| 1 | The one ring | 3 | 99 |
| 2 | Longsword | 1 | 90 |
| 3 | Waistcloth | 3 | 10 |
| 4 | Nazguls Mask | 2 | 95 |
|__________________________________________________|
Now what I want is to make a select command using IN condition, which will give me list of equipments that the characters are not eligible to use. So in this very example, it would be Smeagle being ineligible to wear the one ring and Legolas unable to wield the nazguls mask.
My command looks something like this
SELECT equip_name, equip_owner, required
FROM EQUIPMENT WHERE required IN (SELECT char_rank MY_CHARACTER);
Now this will only print out the equipment where required=char_rank however i want to print out a select, where required equipment level is higher than char_rank instead. Any idea how? Ideally using the IN condition.
EDIT: To clear out confusion regarding where I want, basically what my command does right now is it checks if EQUIPMENT.required=MY_CHARACTER.char_rank what I want instead is that it checks EQUIPMENT.required>MY_CHARACTER.char_rank
I would do this with a join:
select c.*, equip_id as notEligible
from my_character c join
equipment e
on c.char_rank < e.required;
So if you want a list of items, that a certain character is not allowed to use you can slightly modify your SQL:
SELECT
equip_name, equip_owner, required
FROM
EQUIPMENT
WHERE
required > (SELECT char_rank FROM MY_CHARACTER WHERE char_id=?);

SQL 'COUNT' not returning what I expect, and somehow limiting results to one row

Some background: an 'image' is part of one 'photoshoot', and may be a part of zero or many 'galleries'. My tables:
'shoots' table:
+----+--------------+
| id | name |
+----+--------------+
| 1 | Test shoot |
| 2 | Another test |
| 3 | Final test |
+----+--------------+
'images' table:
+----+-------------------+------------------+
| id | original_filename | storage_location |
+----+-------------------+------------------+
| 1 | test.jpg | store/test.jpg |
| 2 | test.jpg | store/test.jpg |
| 3 | test.jpg | store/test.jpg |
+----+-------------------+------------------+
'shoot_images' table:
+----------+----------+
| shoot_id | image_id |
+----------+----------+
| 1 | 1 |
| 1 | 2 |
| 3 | 3 |
+----------+----------+
'gallery_images' table:
+------------+----------+
| gallery_id | image_id |
+------------+----------+
| 1 | 1 |
| 1 | 2 |
| 2 | 3 |
| 3 | 1 |
| 4 | 1 |
+------------+----------+
What I'd like to get back, so I can say 'For this photoshoot, there are X images in total, and these images are featured in Y galleries:
+----+--------------+-------------+---------------+
| id | name | image_count | gallery_count |
+----+--------------+-------------+---------------+
| 3 | Final test | 1 | 1 |
| 2 | Another test | 0 | 0 |
| 1 | Test shoot | 2 | 4 |
+----+--------------+-------------+---------------+
I'm currently trying the SQL below, which appears to work correctly but only ever returns one row. I can't work out why this is happening. Curiously, the below also returns a row even when 'shoots' is empty.
SELECT shoots.id,
shoots.name,
COUNT(DISTINCT shoot_images.image_id) AS image_count,
COUNT(DISTINCT gallery_images.gallery_id) AS gallery_count
FROM shoots
LEFT JOIN shoot_images ON shoots.id=shoot_images.shoot_id
LEFT JOIN gallery_images ON shoot_images.image_id=gallery_images.image_id
ORDER BY shoots.id DESC
Thanks for taking the time to look at this :)
You are missing the GROUP BY clause:
SELECT
shoots.id,
shoots.name,
COUNT(DISTINCT shoot_images.image_id) AS image_count,
COUNT(DISTINCT gallery_images.gallery_id) AS gallery_count
FROM shoots
LEFT JOIN shoot_images ON shoots.id=shoot_images.shoot_id
LEFT JOIN gallery_images ON shoot_images.image_id=gallery_images.image_id
GROUP BY 1, 2 -- Added this line
ORDER BY shoots.id DESC
Note: The SQL standard allows GROUP BY to be given either column names or column numbers, so GROUP BY 1, 2 is equivalent to GROUP BY shoots.id, shoots.name in this case. There are many who consider this "bad coding practice" and advocate always using the column names, but I find it makes the code a lot more readable and maintainable and I've been writing SQL since before many users on this site were born, and it's never cause me a problem using this syntax.
FYI, the reason you were getting one row before, and not getting and error, is that in mysql, unlike any other database I know, you are allowed to omit the group by clause when using aggregating functions. In such cases, instead of throwing a syntax exception, mysql returns the first row for each unique combination of non-aggregate columns.
Although at first this may seem abhorrent to SQL purists, it can be incredibly handy!
You should look into the MySQL function group by.

Table has pairs of matching records, need to select and update only one record

I have a table with pairs of matching records that I query like this:
select id,name,amount,type from accounting_entries
where name like "%05" and amount != 0 order by name limit 10;
Results:
+------+----------------------+--------+-------+
| id | name | amount | type |
+------+----------------------+--------+-------+
| 786 | D-1194-838HELLUJP-05 | -5800 | DEBIT |
| 785 | D-1194-838HELLUJP-05 | -5800 | DEBIT |
| 5060 | D-1195-UOK4HS5POF-05 | -5000 | DEBIT |
| 5059 | D-1195-UOK4HS5POF-05 | -5000 | DEBIT |
| 246 | D-1196-0FUCJI66BX-05 | -7000 | DEBIT |
| 245 | D-1196-0FUCJI66BX-05 | -7000 | DEBIT |
| 9720 | D-1197-W2J0EC1BOB-05 | -6500 | DEBIT |
| 9719 | D-1197-W2J0EC1BOB-05 | -6500 | DEBIT |
| 2694 | D-1198-MFKIKHGW0S-05 | -5500 | DEBIT |
| 2693 | D-1198-MFKIKHGW0S-05 | -5500 | DEBIT |
+------+----------------------+--------+-------+
10 rows in set (0.01 sec)
I need to perform an update so that the resulting data will look like this:
+------+----------------------+--------+--------+
| id | name | amount | type |
+------+----------------------+--------+--------+
| 786 | D-1194-838HELLUJP-05 | -5800 | DEBIT |
| 785 | C-1194-838HELLUJP-05 | 5800 | CREDIT |
| 5060 | D-1195-UOK4HS5POF-05 | -5000 | DEBIT |
| 5059 | C-1195-UOK4HS5POF-05 | 5000 | CREDIT |
| 246 | D-1196-0FUCJI66BX-05 | -7000 | DEBIT |
| 245 | C-1196-0FUCJI66BX-05 | 7000 | CREDIT |
| 9720 | D-1197-W2J0EC1BOB-05 | -6500 | DEBIT |
| 9719 | C-1197-W2J0EC1BOB-05 | 6500 | CREDIT |
| 2694 | D-1198-MFKIKHGW0S-05 | -5500 | DEBIT |
| 2693 | C-1198-MFKIKHGW0S-05 | 5500 | CREDIT |
+------+----------------------+--------+--------+
10 rows in set (0.01 sec)
One entry should negate the other entry. It doesn't matter if I update the first or second matching record, what matters is that one has a positive amount and the other has a negative amount. And the type and name need to be updated.
Any clues on how to do this? What would the update command look like? Maybe using a group by clause? I have some ideas on how to do it with a stored procedure, but can I do it with a simple update?
Try this:
UPDATE accounting_entries as ae
SET name = 'C' + SubString(name, 1, Length(name) - 1))
amount = amount * -1
type = 'Credit'
WHERE id =
(SELECT MIN(id) FROM
(SELECT * FROM accounting_entries) as temp
GROUP BY name)
The key is the subquery in the WHERE section that limits the updates to the lowest ID of each name value. The assumption is that the lower ID is the one that you will always want to update. If this is not correct, then update the subquery based on whatever rule you would use.
Edit: Update to subquery based on technique found here, due to limitation on mysql defined here.
This query gives a method for updating all records at once (as it seemed like this is what the OP was looking for. However, the most efficient way to do this would be to enumerate through all records in code (php, asp.net, etc), and through code-based methods update the rows that needed to change. This would eliminate the performance issues inherent with running updates off of subqueries in mysql.
If the ID:s for a pair always match the formula x and x+1, you could say something like
WHERE MOD(`id`, 2) = 1
EDIT: I haven't tested this code, so I can't guarantee that it's possible to put a column name into a MOD like this, but it might be worth a try, and/or further investigation.
Does this constraint hold true all the time (D == -C) ?
If so, you do not need to keep redundant data in your table, store only one "amount" value (for example the debit):
786 | 1194-838HELLUJP-05 | -5800
and then, on the application level, append a D- to the name and get the raw amount or append a C- and get the - amount.

In MYSQL, how do I get a LEFT JOIN to return every row in one table, and a flag if there were any matching rows in another table?

Basically, I have two tables, admin_privilege and admin_roll_privilege. I'm trying to write a query to get every row from admin_privilege, and if there is a row in admin_roll_privilege with a matching admin_privilege_id AND a matching admin_roll_id, to set a new column to 1. So far, I have this:
SELECT ap.*,
IF(arp.admin_privilege_id IS NULL,0,1) AS has_privilege
FROM admin_privilege ap LEFT JOIN admin_roll_privilege arp
ON ap.admin_privilege_id=arp.admin_privilege_id
WHERE arp.admin_roll_id=3
OR arp.admin_roll_id IS NULL;
This works in every case except where there are no matching rows admin_roll_privilege.
See Example:
+---------------+--------------------+
| admin_roll_id | admin_privilege_id |
+---------------+--------------------+
| 1 | 2 |
| 1 | 3 |
+---------------+--------------------+
+--------------------+------------------------+
| admin_privilege_id | admin_privilege_name |
+--------------------+------------------------+
| 1 | Access Developer Tools |
| 4 | Edit System Settings |
| 2 | Edit User Profiles |
| 3 | Resolve Challenges |
+--------------------+------------------------+
Querying for WHERE admin roll id=1 works as expected:
+--------------------+------------------------+---------------+
| admin_privilege_id | admin_privilege_name | has_privilege |
+--------------------+------------------------+---------------+
| 1 | Access Developer Tools | 0 |
| 4 | Edit System Settings | 0 |
| 2 | Edit User Profiles | 1 |
| 3 | Resolve Challenges | 1 |
+--------------------+------------------------+---------------+
But, if i query for admin_roll_id=3, i only get two rows returned:
+--------------------+------------------------+---------------+
| admin_privilege_id | admin_privilege_name | has_privilege |
+--------------------+------------------------+---------------+
| 1 | Access Developer Tools | 0 |
| 4 | Edit System Settings | 0 |
+--------------------+------------------------+---------------+
How can I get this query to return all 4?
Edit: This is what ended up working, moving the condition to the on clause:
SELECT ap.*,
IF(arp.admin_privilege_id IS NULL,0,1) AS has_privilege
FROM admin_privilege ap LEFT JOIN admin_roll_privilege arp
ON (ap.admin_privilege_id=arp.admin_privilege_id AND arp.admin_roll_id=1)
Move the appropriate conditions from the WHERE clause to the ON clause.
You are not returning all rows by using the WHERE clause on the entire statement.
Turn the LEFT JOIN into a subselect on wich you can add the WHERE clause you need.
SELECT ap.admin_privilege_id
, ap.admin_privilege_name
, IF(arp.admin_privilege_id IS NULL,0,1) AS has_privilege
FROM admin_privilege ap
LEFT OUTER JOIN (
SELECT admin_privilege_id
FROM admin_roll_privilege arp
WHERE arp.admin_roll_id = 3
) arp ON arp.admin_privilege_id = ap.admin_privilege_id