MS Access - Group By and Sum if all values are available query - ms-access

I am trying to create a query in ms access which sums up costs but want to exclude those that have no value from the result. I am struggling because I want to exclude items from one column based on another column.
I have a column with finished product, column with components that make up finished product, column with quantity of components required for finished product and column with cost for each of components. What I need is to get total cost for each component required and then sum up costs as total cost for finished product which is simple enough.
However there are some blank fields where cost for one or more components are not available, I want those to show in the result with text " Pending " for finished product instead of it summing up only available values.
Below example of what I am trying to do:
BOM_List
What I need in result is below:
BOM_Result
Would really appreciate any help on this :)

Elaborating on June 7ths comment and assuming Table2:
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| ID | FinishedProductid | ItemNumber | Component | Uom | ComponentQuantity | ComponentUnit | stdCost |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 1 | 199127402 | 10 | 123123123 | PC | 3 | PC | $1.50 |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 2 | 199127402 | 20 | 321321321 | PC | 1 | PC | $2.50 |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 3 | 199127402 | 30 | 123456789 | PC | 1 | PC | $3.55 |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 4 | 199127402 | 40 | 987654321 | PC | 0.25 | H | $82.00 |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 5 | 199127403 | 10 | 111222333 | PC | 3 | PC | $1.50 |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 6 | 199127403 | 20 | 333222111 | PC | 1 | PC | |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 7 | 199127403 | 30 | 444555666 | PC | 1 | PC | $3.55 |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 8 | 199127403 | 40 | 666555444 | PC | 0.25 | H | $82.00 |
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
'resulting sql to get all FinishedProductids
SELECT Table2.FinishedProductid
FROM Table2
GROUP BY Table2.FinishedProductid;
'once have FinishedProductid's out of the group statement you can just use a calculated variable
SELECT Query2.FinishedProductid, getTotalCost([FinishedProductid]) AS TotalCost
FROM Query2;
'of course a getTotalCost function is needed. Add the following to a code module
Public Function getTotalCost(FinishedProductid As Long) As String
If isPending(FinishedProductid) Then
getTotalCost = "Pending"
Else
getTotalCost = DSum("ComponentQuantity*stdCost", "Table2", "FinishedProductid = " & FinishedProductid)
End If
End Function
Public Function isPending(FinishedProductid) As Boolean
' if any values of stdCost are null set isPending to true
'public functions can be accessed most anywhere in Access
Dim nullCount As Long
nullCount = DCount("ID", "Table2", "FinishedProductid = " & FinishedProductid & " AND ISNULL(stdCost)")
If nullCount > 0 Then
isPending = True
Else
isPending = False
End If
End Function
'result
-------------------------------------------------------
| FinishedProductid | TotalCost |
-------------------------------------------------------
| 199127402 | 31.05 |
-------------------------------------------------------
| 199127403 | Pending |
-------------------------------------------------------
Explanation: I use a two query approach to get around the sql complications that arise from the group by. The functions are relatively declaritive and hence self-commenting. Finally, functions can be reused.
Edit: getTotalCost returns a string so it can meet the requirement of returning both the string "pending" and the total cost

Related

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

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;

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=?);

Issue with UNION in MySQL

I have two tables.
rp_format
+-----+--+--------------+
| fid | | recordformat |
+-----+--+--------------+
| 1 | | CD |
| 2 | | Vinyl |
| 3 | | DVD |
+-----+--+--------------+
rp_records
+----+--+--------+
| id | | format |
+----+--+--------+
| 1 | | 1 |
| 2 | | 2 |
| 3 | | 3 |
+----+--+--------+
What I would like to achieve is to display everything from "rp_format". But I would also like make a check to see if there is a "fid"-value found in "format".
Example that should be displayed on page like this:
fid recordformat
1 CD Remove this format
2 Vinyl Remove this format
3 DVD Remove this format
But let's say an "fid" value is found in "format" then I would like it to be displayed like this on page:
fid recordformat
1 CD Remove this format
2 Vinyl Can't remove this format
3 DVD Remove this format
"Remove this format / Can't remove this format" is text that will be displayed by checking if "fid" = "format" using PHP.
Here is my SQL query so far:
global $wpdb;
$rpdb = $wpdb->prefix . 'rp_format';
$rpdb2 = $wpdb->prefix . 'rp_records';
$sql = "
SELECT *
FROM $rpdb
LEFT OUTER JOIN $rpdb2 ON $rpdb.fid = $rpdb2.format
UNION
SELECT *
FROM $rpdb
RIGHT OUTER JOIN $rpdb2 ON $rpdb.fid = $rpdb2.format
WHERE $rpdb.fid IS NOT NULL
";
The issue I have with this query is that when "fid" is found in "format" (let's say it's found 10 times) every of these 10 values will be outputed also.
How can this be fixed?
Kind regards
Johan
If I understand correctly you want to display some message depending on if the data exists on rp_records or not and avoid multiple display.
Consider the following
mysql> select * from rp_format;
+------+--------------+
| fid | recordformat |
+------+--------------+
| 1 | CD |
| 2 | Vinyl |
| 3 | DVD |
| 4 | Test |
+------+--------------+
4 rows in set (0.00 sec)
mysql> select * from rp_records;
+------+--------+
| id | format |
+------+--------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 2 |
| 5 | 1 |
+------+--------+
So the query is
select
f.*,
case
when r.format is not null then 'Can\'t remove' else 'Remove this' end
as message
from rp_format f
left join rp_records r on r.format = f.fid
group by f.fid ;
+------+--------------+--------------+
| fid | recordformat | message |
+------+--------------+--------------+
| 1 | CD | Can't remove |
| 2 | Vinyl | Can't remove |
| 3 | DVD | Can't remove |
| 4 | Test | Remove this |
+------+--------------+--------------+
Not sure that i correctly understand your logic with found and not found format, if i wrong - add to if condition r.format IS NOT NULL instead r.format IS NULL. And i think you no need to use union, you should use join:
SELECT
r.fid,
f.recordformat,
IF(r.format IS NULL, "Can't remove this format", "Remove this format")
FROM rp_format f
LEFT JOIN rp_records r ON f.fid = r.format
GROUP BY f.fid
;
I'm sure that something like this will help you!

MySQL: GROUP BY with custom hierarchical functionality

I've got a permission/privileges - table looking like this:
+----+----------+----------+------+-------+
| id | name | usertype | read | write |
+----+----------+----------+------+-------+
| 1 | test | A | 0 | 0 |
| 2 | test | MU | 1 | 1 |
| 3 | test | U | 1 | 1 |
| 4 | apple | A | 1 | 1 |
| 5 | apple | MU | 1 | 0 |
| 6 | apple | U | 0 | 0 |
| 7 | flower | A | 0 | 0 |
| 8 | flower | MU | 0 | 0 |
| 9 | flower | U | 1 | 1 |
+----+----------+----------+------+-------+
there are 3 usertypes: A (admin), MU (maintenance user), U (standard user)
the usertypes are hierarchical: A > MU > U
(the usertypes are saved as CHAR(2) in the database, and unfortunately I can't change that)
now i want to build a query which implements the hierarchical logic of my usertypes.
e.g. usertype 'A' got no permission to read or write on stuff with the name 'test', thus usertypes 'MU' AND 'U' also should have no permission for that and their read = 1 and write = 1 should be ignored.
I know which usertype is currently logged in.
I somehow have to check for the minimum of read/write rights to the name for all hierarchical predecessors, i guess. but i don't know how to check that since usertype is not a number field.
this is what I've tried so far:
SELECT
name,
MIN(read),
MIN(write),
CASE
WHEN usertype = 'A' THEN 0
ELSE (CASE
WHEN usertype = 'WU' THEN 1
ELSE 2
END)
END userval
FROM
permissions
-- WHERE usertype <= :current_usertype
GROUP BY name
this seems to work, but i don't know how i can get my condition WHERE usertype <= :current_usertype working, so a usertype down in the hierarchy can't get more privileges on a name than a "higher" usertype.
any ideas?
thanks in advance!
This is how I solved my problem:
1. I added another table "permission_groups" to the database:
+----+----------+--------+
| id | usertype | value |
+----+----------+--------+
| 1 | A | 100 |
| 2 | MU | 20 |
| 3 | U | 10 |
+----+----------+--------+
2. Then I joined this table to my original table "permissions" which i showed in my question:
here i get the value of my "permission_groups" table with a subquery. this value symbolizes the hierarchical order of my different usertypes.
SELECT
perm.name,
MIN(perm.`read`),
MIN(perm.`write`),
group .value
FROM
permissions perm
LEFT JOIN permission_groups group ON group.usertype = perm.usertype
WHERE
group.value >= (SELECT value from permission_groups WHERE usertype = :current_usertype)
GROUP BY perm.name
:current_usertype is a PDO parameter in my case, which is replaced by the usertype of the current user.

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.