SQL join two tables by modifying on columns - sql

I have 3 tables on PostgreSQL:
SPENDINGS:
store |spending
-----------+---------
0000700551 | $75
STORE:
store | zip_code
-------+---------
700551 | XXP PDD
CUSTOMER:
id | zip_code
----+----------
002 | XXPPDD
I would love to join these tables like this:
right(SPENDIGNS.store, 6) = STORE.store and
trim(STORE.zip_code) = CUSTOMER.zip_code.
How can I do that could you help me out please?

trim(store.zip_code) is not good for the job because the whitespace is within the zip code text. Use replace(store.zip_code, ' ', '') instead. right(spendings.store, 6) does not seem safe to me too. Better use ltrim(spendings.store, '0') to remove leading zeroes. SQL Fiddle
select *
from spendings sp
join store st on ltrim(sp.store, '0') = st.store
join customer cu on cu.zip_code = replace(st.zip_code, ' ', '');
BTW your data design does need improvement.

try something as follows (you can use what ever join you would like to in place of inner)
select spendings.spending, store.store,customer.zipcode
from spendings inner join store on right(SPENDIGNS.store, 6) = STORE.store
inner join customer on trim(STORE.zip_code) = CUSTOMER.zip_code

Related

Include indicator of data present, but not include in results

I can't quite explain it properly, hence the awful title.
As an example, I would have a query that shows customer complaints, of a certain type, and the dates since those complaints. Something like the following:
select * from( select c.firstname, c.lastname , max(ont.date1) as "LastDate",
DATEDIFF(DAY, MAX(ont.Date1), SYSDATETIME()) AS "Days"
from [ComplaintInfo] ci
inner join [OrderNotes] ont on ont.orderid = ci.orderid
inner join [Customers] c on c.custid = ci.custid
right outer join [CustLive] cl on ont.custidl = cl.custidl
where (ci.typeofcomp = '2' or ci.typeofcomp = '3')
and (ont.answertype <> '2' and ont.answertype <> '3' and ont.answertype <>'4'
group by c.lastname, c.firstname
) Sub
where Days >= 5
order by Days, sub.lastname asc
This would give something like
John | Smith | 2020-06-03T13:00:00 | 1
Terry | Jones | 2020-05-04T:04:00:00 | 30
However, although I'm wanting typeoforder to NOT be 2 or 3, and I don't want them to be included in my result set, I would like to know whether or not there have been any orders of those types. So, if for example
John | Smith |2020-06-03T13:00:00 | 1 | Yes
Terry | Jones | 2020-05-04-04:00:00 | 30 | No
Could just be asking a stupid question, but need to know. Thanks.
You can remove the condition from the where clause and use a conditional expression:
select
c.firstname,
c.lastname,
max(ont.date1) as LastDate
datediff(day, max(ont.date1), sysdatetime()) as days,
max(case when ci.typeofcomp = 2 or ci.typeofcomp = 3 then 'Yes' else 'No' end) hasTypeOfComp_2_Or_3
from [ComplaintInfo] ci
inner join [OrderNotes] ont on ont.orderid = ci.orderid
inner join [Customers] c on c.custid = ci.custid
right outer join [CustLive] cl on ont.custidl = cl.custidl
where ont.answertype <> 2 and ont.answertype <> 3 and ont.answertype <> 4
group by c.lastname, c.firstname
Notes:
I removed the single quotes around the numbers - since they look like numbers, I assumed that they are stored as such, in which case you want to compare them against literal numbers (if that's not the case, you can put the quotes back)
I am suspicious about the right outer join: is this really what you want? If not, just use an inner join

Is there an alternative for XML PATH in SQL

I am developing a clinic management application.
In that application there is a part where doctors choose what they use up in each service they provide.
For example, when a doctor examines a patient, a pair of gloves and wooden stick are used and disposed of.
I use a trick that is simply, I create a string in the service table, that string has the IDs separated with comma (something like that 1,3,7,) and it works fine except for one case.
That case is when I want to display the used up item names as one column for multiple services.
I get items of service one plus service two and so on as one string for all service.
Is there any way I can solve that?
I know it sounds complicated but If it works it will many other you read this.
The way I use to join is:
on CHARINDEX(CAST(TblSupply.IDSupply as varchar(10)), TblService.UsedSupplyIDs) > 0
Simply I cast the Used Item ID TblSupply.IDSupply as string then check if it exists in the service table.
When I display as separate columns, it works fine but I get multiple rows for the same service as follows:
Select
TblService.ServiceName,
TblSupply.SupplyName
from
TblService
left join TblSupply on CHARINDEX(CAST(TblSupply.IDSupply as varchar(10)), TblService.UsedSupplyIDs) > 0
e.g.
_____________________________________________
|TblService.ServiceName|TblSupply.SupplyName |
|Patient examination |Glove |
|Patient examination |wood stick |
|Patient examination |thermometer |
|Sonar examination |Glove |
|Sonar examination |lubricating Gel |
|Consultation |Glove |
|______________________|_____________________|
I want to get
________________________________________________________
|TblService.ServiceName|xxxxx |
|Patient examination |Glove ,wood stick , thermometer |
|Sonar examination |Glove,lubricating Gel |
|Consultation |Glove |
The code I made
Select
TblService.ServiceName,TempTable.SupplyList
from
TblService
left join (Select TblService.IDService As IDService,
STUFF((Select ', ' + TblSupply.SupplyName
FROM
TblService
left join TblSupply on CHARINDEX(CAST(TblSupply.IDSupply as varchar(10)), TblService.UsedSupplyIDs) > 0
FOR XML PATH('')), 1, 1, '') as SupplyList
FROM
TblService
GROUP BY
TblService.IDService
) as TempTable on TempTable.IDService=TblService.IDService
I tried
Select
TblPatientService.IDPatientService,
TblService.ServiceName,
TempTable.SupplyList
from
TblPatientService
left Join TblService On TblService.IDService = TblPatientService.IDService
left join (Select TblPatientService.IDPatientService As IDPatientService
STUFF((Select ', ' + TblSupply.SupplyName
FROM
TblPatientService
left join TblService on TblService.IDService = TblPatientService.IDService
left join TblSupply on CHARINDEX(CAST(TblSupply.IDSupply as varchar(10)), TblService.UsedSupplyIDs) > 0
WHERE
TblPatientService.IDService = TblService.IDService
FOR XML PATH('')), 1, 1, '') as SupplyList
FROM
TblPatientService
left Join TblService On TblService.IDService = TblPatientService.IDService
GROUP BY
TblPatientService.IDPatientService
) as TempTable on TempTable.IDPatientService = TblPatientService.IDPatientService
What I really get
|TblService.ServiceName|xxxxx
|Patient examination |Glove ,wood stick , thermometer,Glove,lubricating Gel,Glove,lubricating Gel |
|Sonar examination |Glove ,wood stick , thermometer,Glove,lubricating Gel,Glove,lubricating Gel |
|Consultation |Glove ,wood stick , thermometer,Glove,lubricating Gel,Glove,lubricating Gel |
In other words I get all the used items for all services as if they used for one service for all displayed.
Since SQL Server 2017, you can use STRING_AGG. Somtehing like :
SELECT TblPatientService.IDPatientService AS IDPatientService,
STRING_AGG(TblSupply.SupplyName, ', ') AS SupplyList
FROM TblPatientService
LEFT OUTER JOIN TblService ON TblService.IDService = TblPatientService.IDService
LEFT OUTER JOIN TblSupply ON CHARINDEX(CAST(TblSupply.IDSupply AS VARCHAR(10)), TblService.UsedSupplyIDs) > 0
GROUP BY TblPatientService.IDPatientService;
To replace XML stuff !
This is too long for a comment.
I would strongly suggest fixing your data model. Storing delimited lists in database columns is a typical design antipattern, that will backfire in different ways (complexity, efficiency, maintenance, data integrity). You can ha have a look at this famous SO question for more details.
As an exemple: your current join condition will not do what you expect for some supply ids that are more than one digit long (typically, 1 would match 10).
You should normalize your model and have a separate table to store the N-M relationship between services and supplies, with one tuple per row.

Select highest value based off of a different column

I am trying to get the highest value based off of another column.
SELECT DISTINCT
AppDetailVehicleValuation.AppID,
VehicleValuationOption.Description,
MAX (VehicleValuationOptionValueType.Value)
FROM
AppDetailVehicleValuation
INNER JOIN VehicleValuationOption
ON AppDetailVehicleValuation.ValuationID = VehicleValuationOption.ValuationID
INNER JOIN VehicleValuationOptionValueType
ON VehicleValuationOption.ValuationOptionID = VehicleValuationOptionValueType.ValuationOptionID
WHERE
(VehicleValuationOption.IsSelected LIKE '1')
AND (VehicleValuationOption.IsSystemOption LIKE '1')
What I have is this
AppID | Description | Value
999 Beats Audio 425.00
999 Beats Audio 475.00
999 Power Str. 600.00
999 Power Str. 750.00
this is what I need
AppID | Description | Value
999 Beats Audio 475.00
999 Power Str. | 750.00
You are just missing a GROUP BY clause in your query:
SELECT
AppDetailVehicleValuation.AppID,
VehicleValuationOption.Description,
MAX (VehicleValuationOptionValueType.Value)
FROM
AppDetailVehicleValuation
INNER JOIN VehicleValuationOption
ON AppDetailVehicleValuation.ValuationID = VehicleValuationOption.ValuationID
INNER JOIN VehicleValuationOptionValueType
ON VehicleValuationOption.ValuationOptionID = VehicleValuationOptionValueType.ValuationOptionID
WHERE
(VehicleValuationOption.IsSelected LIKE '1')
AND (VehicleValuationOption.IsSystemOption LIKE '1')
GROUP BY AppDetailVehicleValuation.AppID, VehicleValuationOption.Description
You can simply do this:
SELECT
t.AppId,
t.Description,
max(t.Value)
FROM mytable t
GROUP BY t.description, t.AppId
This is too long for a comment.
Glad you found an answer that works with the GROUP BY. I would suggest you start using aliases in your queries. It can quickly and easily turn a wall of text into something fairly easy to see what is going on. You might end with something along these lines.
SELECT advv.AppID
, vvo.Description
, MaxValue = MAX(vvot.Value)
FROM AppDetailVehicleValuation advv
INNER JOIN VehicleValuationOption vvo ON advv.ValuationID = vvo.ValuationID
INNER JOIN VehicleValuationOptionValueType vvot ON vvo.ValuationOptionID = vvot.ValuationOptionID
WHERE vvo.IsSelected = '1'
AND vvo.IsSystemOption = '1'
group by advv.AppID
, vvo.Description

How to conditionally join two tables

I have two tables which I need to join depending upon their values.
TABLE coursemat
+-----+--------+----------+
| txt | price | material |
+-----+--------+----------+
Table coprices
+--------+----------+
| price | material |
+--------+----------+
They are connected by the material key.
If I search coursemat.material and find that coprices.material is equal, then I must use coprices.price instead of the coursemat.price.
This is what I have so far:
SELECT coursemat.txt, coursemat.price, coursemat.material, coprices, country
FROM coursemat
JOIN corprices ON coursemat.material = coprices.material;
But this isn't quite getting what I want.
Essentially, I want to use coursemat.price if coprices.price does not exist for the same material and coprices.material does exist, then I want to use coprices.price instead of coursemat.price.
If I understand what you want correctly you can use a left join and the IFNULL statement:
SELECT
coursemat.txt,
IFNULL(coprices.price, coursemat.price),
coursemat.material,
coprices.country
FROM
coursemat
LEFT JOIN
corprices
ON coursemat.material = coprices.material;
Another option is COALESCE:
SELECT cm.txt, cm.material, COALESCE(cp.price, cm.price) AS price
FROM coursemat cm
LEFT JOIN corprices cp ON cm.material = cp.material;
Finally, you could also use a CASE statement:
SELECT cm.txt, cm.material,
CASE WHEN cp.price IS NOT NULL THEN cp.price ELSE cm.price END AS price
FROM coursemat cm
LEFT JOIN corprices cp ON cm.material = cp.material;

Query table with all names in one statement

I have a table that I cannot alter and I am trying to wrap my head around an appropriate way to query this table in one statement based on one given value.
Roughly here's what the table looks like:
What I'm after is a way to query the table using only the SubBody code in order to get all names associated with it.
for example,
<query> where SubBody = '1001'
returns
| HName | HSName | BName | BSName |
+-----------------------------------+
| Toys | Sport | Ball | Baseball |
SELECT Head.Name as HName ,
SubHead.Name as HSName ,
Body.Name as BName ,
SubBody.Name as BSName
FROM yourTable as SubBody
JOIN yourTable as Body
ON SubBody.Body = Body.Body
AND Body.SubBody IS NULL
JOIN yourTable as SubHead
ON Body.SubHead = SubHead.SubHead
AND SubHead.Body IS NULL
JOIN yourTable as Head
ON SubHead.Head = Head.Head
AND Head.SubHead IS NULL
WHERE SubBody.SubBody = '1001'
Although you can express the logic as joins, for some reason correlated subqueries come to mind first:
select (select th.name from t th where th.head = t.head and th.subhead is null) as hname,
(select ts.name from t ts where ts.head = t.head and ts.subhead = t.subhead and t.body is null) as sname,
(select tb.name from t tb where tb.head = t.head and tb.subhead = t.subhead and tb.body = t.body and tb.subbody is null) as bname,
t.name as bsname
from t
where t.subbody = 1001
This answer is close to what Juan Carlos has posted. Which one you like depends a bit on your style.
WITH BaseRecord AS(
SELECT *
FROM ToyTable
WHERE SubBody = '1001'
)
SELECT
h.Name AS HName,
hs.Name AS HSName,
b.Name AS BName,
bs.Name AS BSName
FROM BaseRecord bs
INNER JOIN ToyTable b ON bs.Body = b.Body AND ISNULL(b.SubBody,'') = ''
INNER JOIN ToyTable hs ON bs.SubHead = hs.SubHead AND ISNULL(hs.Body,'') = ''
INNER JOIN ToyTable h ON bs.Head = h.Head AND ISNULL(h.SubHead,'') = ''
I'm not sure whether your "empty" cells contain nulls or empty strings. Either way, both are taken into account here.