Parentheses affecting query results - sql

I am confused by the results of a query I am running. Hopefully this doesn't end with me slapping my head and feeling like an idiot, but here goes (SQL Server 2008).
First query was this:
SELECT p.product_number,p.long_desc
FROM products p
WHERE p.prod_status = 1
AND ((p.long_desc IS NULL) OR (p.long_desc LIKE '%N/A%'))
ORDER BY p.product_number
Second version is this:
SELECT p.product_number,p.long_desc
FROM products p
WHERE p.prod_status = 1
AND p.long_desc IS NULL
OR p.long_desc LIKE '%N/A%'
ORDER BY p.product_number
There are three products in the second version that do not appear in the first, yet to me these two queries should give identical results. The three items that appear in the second but not the first all have the value N/A in the long_desc column.
However there are many others with N/A as well and show up in both versions.
What don't I understand about the use of parenthesis here?

AND has a higher precedence than OR (see documentation), so without the parentheses, the query is equivalent to this:
SELECT p.product_number,p.long_desc
FROM products p
WHERE (p.prod_status = 1
AND p.long_desc IS NULL)
OR p.long_desc LIKE '%N/A%'
ORDER BY p.product_number

Related

SQL getting a percentage from a column of quantities

I have a set of shelves and some are not being used but some are. I want to get the percentage of shelves that are being used (I am using an ajax call from javascript) what is a good way to do this and could you please provide an example?
This is the query I have so far which gets the nulls and sets the quantity to 0:
SELECT warehouse_locations.location, ISNULL(product_stock_warehouse.quantity, 0) as quantity
FROM product_stock_warehouse
RIGHT JOIN warehouse_locations ON product_stock_warehouse.location = warehouse_locations.location
WHERE warehouse_locations.location LIKE 'A21%'
ORDER BY product_stock_warehouse.quantity
If the shelf is not 0, it is "Full" and therefore counts towards the percentage being used.
I am using MS-SQL
I think you need aggregation. The idea is something like this:
SELECT wl.location, COALESCE(SUM(psw.quantity), 0) as total_quantity,
(CASE WHEN COALESCE(SUM(psw.quantity), 0) = 0 THEN 'EMPTY' ELSE 'USED' END) as status
FROM warehouse_locations wl LEFT JOIN
product_stock_warehouse psw
ON psw.location = wl.location
WHERE wl.location LIKE 'A21%'
GROUP BY wl.location
ORDER BY psw.quantity;
Some notes:
LEFT JOIN is much easier to follow than RIGHT JOIN. It means "keep all rows in the first table" rather than "keep all rows in some table later in the FROM clause that I haven't seen yet".
Table aliases make a query easier to write and to read.
Use GROUP BY to get one row per "shelf", which I assume is the same as a "location".

Rails 5 equivalent for this complex SQL query?

I have a query working the way I want, by executing SQL directly, but am curious (just for my own learning purposes) if this same thing could be done in an ActiveRecord statement?
The part I'm struggling with the most is the COALESCE part of this query, which just makes sure that any NULL values from the LEFT JOIN are counted as zeros instead, to keep the summation in order.
Any ideas? I'm using Postgres.
SELECT Inventories.id, Inventories.name, Inventories.unit_of_measure,
COALESCE(Sum(Stocks.count),0) as totalcount
FROM Inventories
LEFT JOIN Stocks
ON Inventories.id = Stocks.inventory_id
WHERE Inventories.property = 'material' AND Inventories.organization_id = #{current_organization.id}
GROUP BY Inventories.id, Stocks.inventory_id
ORDER BY totalcount ASC
LIMIT(5)")
This is the closest I've gotten for an AR equivalent. When I try to add a sum or something like it, that's when it errors out.
#lowmaterials = current_organization.inventories.materials.left_joins(:stocks).group(:id, :inventory_id).order(count: :asc).limit(5)
You can use ActiveRecord::QueryMethods#select:
your_relation.select("column1, column2, COALESCE(1,2) AS column3").left_joins...

Trouble with pulling distinct data

Ok this is hard to explain partially because I'm bad at sql but this code isn't doing exactly what I want it to do. I'll try to explain what it is supposed to do as best I can and hopefully someone can spot a glaring mistake. I'm sorry about the long winded explanation but there is a lot going on here and I really could use the help.
The point of this script is to search for parts which need to be obsoleted. in other words they haven't been used in three years and are still active.
When we obsolete part, "part.status" is set to 'O'. It is normally null. Also, the word 'OBSOLETE' is usually written in to "part.description"
The "WORK_ORDER" contains every scheduled work order. These are defined by base,lot, and sub ID's. It also contains many dates such as the date when the work order was closed.
the "REQUIREMENT" table contains all the parts require for each job. many jobs may require multiple parts, some at different legs of the job. The way this is handled is that for a given "REQUIREMENT.WORKORDER_BASE_ID" and "REQUIREMENT.WORKORDER_LOT_ID", they may be listed on a dozen or so subsequent rows. Each line specifies a different "REQUIREMENT.PART_ID". The sub id separates what leg of the job that the part is needed. All of the parts I care about start with 'PCH'
When I run this code it returns 14 lines, I happen to know it should be returning about 39 right now. I believe the screwy part starts at line 17. I found that code on another form hoping that it would help solve the original problem. Without that code, I get like 27K lines because the DB is pulling every criteria matching requirement from every criteria matching work order. Many of these parts are used on multiple jobs. I've also tried using DISTINCT on REQUIREMENT.PART_ID which seems like it should solve the problem. Alas it doesn't.
So I know despite all the information I probably still didn't give nearly enough. Does anyone have any suggestions?
SELECT
PART.ID [Engr Master]
,PART.STATUS [Master Status]
,WO.CLOSE_DATE
,PT.ID [Die]
,PT.STATUS [Die Status]
FROM PART
CROSS APPLY(
SELECT
WORK_ORDER.BASE_ID
,WORK_ORDER.LOT_ID
,WORK_ORDER.SUB_ID
,WORK_ORDER.PART_ID
,WORK_ORDER.CLOSE_DATE
FROM WORK_ORDER
WHERE
GETDATE() - (360*3) > WORK_ORDER.CLOSE_DATE
AND PART.ID = WORK_ORDER.PART_ID
AND PART.STATUS ='O'
)WO
CROSS APPLY(
SELECT
REQUIREMENT.WORKORDER_BASE_ID
,REQUIREMENT.WORKORDER_LOT_ID
,REQUIREMENT.WORKORDER_SUB_ID
,REQUIREMENT.PART_ID
FROM REQUIREMENT
WHERE
WO.BASE_ID = REQUIREMENT.WORKORDER_BASE_ID
AND WO.LOT_ID = REQUIREMENT.WORKORDER_LOT_ID
AND WO.SUB_ID = REQUIREMENT.WORKORDER_SUB_ID
AND REQUIREMENT.PART_ID LIKE 'PCH%'
)REQ
CROSS APPLY(
SELECT
PART.ID
,PART.STATUS
FROM PART
WHERE
REQ.PART_ID = PART.ID
AND PART.STATUS IS NULL
)PT
ORDER BY PT.ID
This is difficult to understand without any sample data, but I took a stab at it anyway. I removed the second JOIN to PART (that had alias PART1) as it seemed unecessary. I also removed the subquery that was looking for parts HAVING COUNT(PART_ID) = 1
The first JOIN to PART should be done on REQUIREMENT.PART_ID = PART.PART_ID as the relationship as already been defined from WORK_ORDER to REQUIREMENT, hence you can JOIN PART directly to REQUIREMENT at this point.
EDIT 03/23/2015
If I understand this correctly, you just need a distinct list of PCH parts, and their respective last (read: MAX) CLOSE_DATE. If that is the case, here is what I propose.
I broke the query up into a couple of CTE's. The first CTE is simply going through the PART table and pulling out a DISTINCT list of PCH parts, grouping by PART_ID and DESCRIPTION.
The second CTE, is going through the REQUIREMENT table, joining to the WORK_ORDER table and, for each PART_ID (handled by the PARTITION) assigning the CLOSE_DATE a ROW_NUMBER in descending order. This will ensure that each ROW_NUMBER with a value of "1" will be the Max CLOSE_DATE for each PART_ID.
The final SELECT statement simply JOINS the two Cte's on PART_ID, filtering where LastCloseDate = 1 (the ROW_NUMBER assigned in the second CTE).
If I understand the requirements correctly, this should give you the desired results.
Additionally, I removed the filter WHERE PART.DESCRIPTION NOT LIKE 'OB%' because we're already filtering by PART.STATUS IS NULL and you stated above that an 'O' is placed in this field for Obsolete parts. Also, [DIE] and [ENGR MASTER] have the same value in the 27 rows being pulled before, so I just used the same field and labeled them differently.
; WITH Parts AS(
SELECT prt.PART_ID AS [ENGR MASTER]
, prt.DESCRIPTION
FROM PART prt
WHERE prt.STATUS IS NULL
AND prt.PART_ID LIKE 'PCH%'
GROUP BY prt.ID, prt.DESCRIPTION
)
, LastCloseDate AS(
SELECT req.PART_ID
, wrd.CLOSE_DATE
, ROW_NUMBER() OVER(PARTITION BY req.PART_ID ORDER BY wrd.CLOSE_DATE DESC) AS LastCloseDate
FROM REQUIREMENT req
INNER JOIN WORK_ORDER wrd
ON wrd.BASE_ID = req.WORKORDER_BASE_ID
AND wrd.LOT_ID = req.WORKORDER_LOT_ID
AND wrd.SUB_ID = req.WORKORDER_SUB_ID
WHERE wrd.CLOSE_DATE IS NOT NULL
AND GETDATE() - (365 * 3) > wrd.CLOSE_DATE
)
SELECT prt.PART_ID AS [DIE]
, prt.PART_ID AS [ENGR MASTER]
, prt.DESCRIPTION
, lst.CLOSE_DATE
FROM Parts prt
INNER JOIN LastCloseDate lst
ON prt.PART_ID = lst.PART_ID
WHERE LastCloseDate = 1

What do OrientDB's functions do when applied to the results of another function?

I am getting very strange behavior on 2.0-M2. Consider the following against the GratefulDeadConcerts database:
Query 1
SELECT name, in('written_by') AS wrote FROM V WHERE type='artist'
This query returns a list of artists and the songs each has written; a majority of the rows have at least one song.
Query 2
Now try:
SELECT name, count(in('written_by')) AS num_wrote FROM V WHERE type='artist'
On my system (OSX Yosemite; Orient 2.0-M2), I see just one row:
name num_wrote
---------------------------
Willie_Cobb 224
This seems wrong. But I tried to better understand. Perhaps the count() causes the in() to look at all written_by edges...
Query 3
SELECT name, in('written_by') FROM V WHERE type='artist' GROUP BY name
Produces results similar to the first query.
Query 4
Now try count()
SELECT name, count(in('written_by')) FROM V WHERE type='artist' GROUP BY name
Wrong path -- So try LET variables...
Query 5
SELECT name, $wblist, $wbcount FROM V
LET $wblist = in('written_by'),
$wbcount = count($wblist)
WHERE type='artist'
Produces seemingly meaningless results:
You can see that the $wblist and $wbcount columns are inconsistent with one another, and the $wbcount values don't show any obvious progression like a cumulative result.
Note that the strange behavior is not limited to count(). For example, first() does similarly odd things.
count(), like in RDBMS, computes the sum of all the records in only one value. For your purpose .size()seems the right method to call:
in('written_by').size()

SQL WHERE subquery with additional refinements?

Is it possible to have a subquery in a WHERE statement with additional qualifiers? For instance, I have two tables on two different machines. I am trying to compare the tables and see which items only exist on one machine, but only a certain type of item. I only sync two types of items. I can run this query and it works:
select
ItemCode as style,
ISNULL(U_certno,'') as U_certno,
U_he, ISNULL(U_mold,'N') as U_mold
from ITEMS Z
where
z.itemcode <> (SELECT distinct Q.style
FROM remote.system.table Q where Q.style = Z.ItemCode)
That much works, I think. If I change the z.itemcode <> to z.itemcode =, I toggle between 0 results (sync is current), and all records. However, when I add:
and (z.U_he like 'y' or z.U_mold like 'y')
it gives me all items in the ITEMS table that have those two criteria, regardless of the subquery. I have tried all manners of parenthesis, to no avail. What am I doing wrong? I didn't bother trying a join, since I want dissimilar items, not matches. Any help would be appreciated. This is MSSQL 2008 R2.
select
ItemCode as style,
ISNULL(U_certno,'') as U_certno,
U_he, ISNULL(U_mold,'N') as U_mold
from ITEMS Z
where not exists (
select 1
from remote.system.table Q
where Q.style = Z.ItemCode
)
and 'y' IN (z.U_he, z.U_mold)