how to make one sql query asking for many rows - sql

I have such table (example and only part of it):
node_id | k | v
123 | addr:housenumber | 50
123 | addr:street | Kingsway
123 | addr:city | London
123 | (some other stuff) | .....
100 | addr:housenumber | 121
100 | addr:street | Edmund St
100 | addr:city | London
I want to find in this table using one query if there exist e.g. London, Kingsway 50 and what's its node_id. How to make such query and is it even possible? How to deal with such problem?
pseudocode:
SELECT node_id WHERE (k == 'addr:city') == 'London' AND (k == 'addr:street') == 'Kingsway' AND (k == 'addr:housenumber') == '50' AND for all node_id the same
Schema for database: http://pastebin.com/Yigjt77f, my table node_tags.

You could use self-joins like this:
SELECT n1.node_id FROM node_tags n1
INNER JOIN node_tags n2 ON n1.node_id = n2.node_id
INNER JOIN node_tags n3 ON n1.node_id = n3.node_id
WHERE n1.k = 'addr:housenumber' AND n1.v = '50'
AND n2.k = 'addr:street' AND n2.v = 'Kingsway'
AND n3.k = 'addr:city' AND n3.v = 'London'
There might be better ways of doing this with PostgreSQL but I'm not that familiar with that DBMS.
Sample SQL Fiddle.

Related

SQL subtracting a value from 2 separate rows, in 2 separate columns

I am having trouble figuring this out... I am trying to write an SQL query to subtract 2 values from this table:
RateTable:
RecordID | Policy | Benefit | CBBR | IBBR
---------+--------+---------+-------+-------
1 | 12345 | A | $1.34 | $5.64
2 | 12345 | B | $4.56 | $0.56
3 | 12345 | C | $5.67 | $3.32
4 | 54321 | A | $2.57 | $6.24
5 | 34512 | A | $1.76 | $3.32
6 | 34512 | A | $4.56 | $1.34
I need to create a query that will return the result from the value in CBBR where Policy = 12345 and benefit = A then subtract the value in IBBR where Policy = 12345 and benefit = B ($1.34 - 0.56)
Any ideas?
I am not clear that you want the diff for only 1 value "B" or the pattern will go on if condition (Policy = 12345 and benefit ='A') to get the diff of next row.
ASSUMING diff you want to calculate for all next rows
select *,case when Benefit='A'AND Policy=12345 then CBBR-new_IBBR else CBBR end as diff from(
select *,lead(IBBR,1) over (order by RecordID ASC) as new_IBBR from <TABLE NAME>)x
Ankit Jindal has already gave a correct answeh. However, I'd like to note that there's no need to use any subquery as they can significantly slown down the performance. In this particular case, a JOIN operator is enough:
SELECT
rta.CBBR-rtb.IBBR as Result
FROM RateTable as rta
JOIN RateTable as rtb ON rtb.Policy=rta.Policy AND rtb.Benefit='B'
WHERE Policy=12345
AND Benefit='A'
As per the details mentioned in query, you need difference between CBBR & IBBR for particular conditions.
SELECT CBBR-
(SELECT IBBR
FROM RateTable
WHERE Policy = 12345
AND benefit = 'B') AS IBBR
FROM RateTable
WHERE Policy = 12345
AND benefit = 'A';
But if you need generalized query then we probably gonna use SUM or something else.
Your question requires more explanation, based on my understanding I think you should use case statement as-
Select
Case when (Policy = 12345 and benefit = A) then CBBR
When (Policy = 12345 and benefit = B) then CBBR-IBBR
END as Value
From Yourtable

Compare Two Relations in SQL

I just started studying SQL and this is a demo given by the teacher in an online course and it works fine. The statement is looking for "students such that number of other students with same GPA is equal to number of other students with same sizeHS":
select *
from Student S1
where (
select count(*)
from Student S2
where S2.sID <> S1.sID and S2.GPA = S1.GPA
) = (
select count(*)
from Student S2
where S2.sID <> S1.sID and S2.sizeHS = S1.sizeHS
);
It seems that in this where clause, we're comparing two relations (because the result of a subquery is a relation), but most of the time we are comparing attributes(as far as I've seen).
So I'm thinking about whether there are requirements for how many attributes, and how many tuples, the RELATION should contain when comparing two RELATIONS. If not, how do we compare two RELATIONS when there're multiple attributes or multiple tuples and what do we get for result?
Note:
Student relation has 4 attributes: sID, sName, GPA, sizeHS. And here's the data:
+-----+--------+-----+--------+
| sID | sName | GPA | sizeHS |
+-----+--------+-----+--------+
| 123 | Amy | 3.9 | 1000 |
| 234 | Bob | 3.6 | 1500 |
| 345 | Craig | 3.5 | 500 |
| 456 | Doris | 3.9 | 1000 |
| 567 | Edward | 2.9 | 2000 |
| 678 | Fay | 3.8 | 200 |
| 789 | Gary | 3.4 | 800 |
| 987 | Helen | 3.7 | 800 |
| 876 | Irene | 3.9 | 400 |
| 765 | Jay | 2.9 | 1500 |
| 654 | Amy | 3.9 | 1000 |
| 543 | Craig | 3.4 | 2000 |
+-----+--------+-----+--------+
and the result of this query is:
+-----+--------+-----+---------+
| sID | sName | GPA | sizeHS |
+-----+--------+-----+---------+
| 345 | Craig | 3.5 | 500 |
| 567 | Edward | 2.9 | 2000 |
| 678 | Fay | 3.8 | 200 |
| 789 | Gary | 3.4 | 800 |
| 765 | Jay | 2.9 | 1500 |
| 543 | Craig | 3.4 | 2000 |
+-----+--------+-----+---------+
because the result of a subquery is a relation
Relation is the scientific name for what we call a table in a database and I like the name "table" much better than "relation". A table is easy to imagine. We know them from our school time schedule for instance. Yes, we relate things here inside a table (day and time and the subject taught in school), but we can also relate tables to tables (pupils' timetables with the table of class rooms, the overall subject schedule, and the teacher's timetables). As such, tables in an RDBMS are also related to each other (hence the name relational database management system). I find the name relation for a table quite confusing (and many people use the word "relation" to describe the relations between tables instead).
So, yes, a query result itself is again a table ("relation"). And from tables we can of course select:
select * from (select * from b) as subq;
And then there are scalar queries that return exactly one row and one column. select count(*) from b is such a query. While this is still a table we can select from
select * from (select count(*) as cnt from b) as subq;
we can even use them where we usually have single values, e.g. in the select clause:
select a.*, (select count(*) from b) as cnt from a;
In your query you have two scalar subqueries in your where clause.
With subqueries there is another distinction to make: we have correlated and non-correlated subqueries. The last query I have just shown contains a non-correlated subquery. It selects the count of b rows for every single result row, no matter what that row contains elsewise. A correlated subquery on the other hand may look like this:
select a.*, (select count(*) from b where b.x = a.y) as cnt from a;
Here, the subquery is related to the main table. For every result row we look up the count of b rows matching the a row we are displaying via where b.x = a.y, so the count is different from row to row (but we'd get the same count for a rows sharing the same y value).
Your subqueries are also correlated. As with the select clause, the where clause deals with one row at a time (in order to keep or dismiss it). So we look at one student S1 at a time. For this student we count other students (S2, where S2.sID <> S1.sID) who have the same GPA (and S2.GPA = S1.GPA) and count other students who have the same sizeHS. We only keep students (S1) where there are exactly as many other students with the same GPA as there are with the same sizeHS.
UPDATE
As do dealing with multiple tuples as in
select *
from Student S1
where (
select count(*), avg(grade)
from Student S2
where S2.sID <> S1.sID and S2.GPA = S1.GPA
) = (
select count(*), avg(grade)
from Student S2
where S2.sID <> S1.sID and S2.sizeHS = S1.sizeHS
);
this is possible in some DBMS, but not in SQL Server. SQL Server doesn't know tuples.
But there are other means to achieve the same. You could just add two subqueries:
select * from student s1
where (...) = (...) -- compare counts here
and (...) = (...) -- compare averages here
Or get the data in the FROM clause and then deal with it. E.g.:
select *
from Student S1
cross apply
(
select count(*) as cnt, avg(grade) as avg_grade
from Student S2
where S2.sID <> S1.sID and S2.GPA = S1.GPA
) sx
cross apply
(
select count(*) as cnt, avg(grade) as avg_grade
from Student S2
where S2.sID <> S1.sID and S2.sizeHS = S1.sizeHS
) sy
where sx.cnt = sy.cnt and sx.avg_grade = sy.avg_grade;
There are relational operations:
The intersection operator produces the set of tuples that two
relations share in common. Intersection is implemented in SQL in the
form of the INTERSECT operator.
The difference operator acts on two relations and produces the set of tuples from the first relation that do not exist in the second relation. Difference is implemented in SQL in the form of the EXCEPT or MINUS operator.
So, in the context of SQL Server, for example, you can do:
SELECT *
FROM R1
EXCEPT
SELECT *
FROM R2
to get rows in R1 not included in R2 and the reverse - to get all differences.
Of course, the attributes must be the same - if not, you need to explicit set the attributes in the SELECT.

Columns to Rows Two Tables in Cross Apply

Using SQL Server I have two tables, below sample Table #T1 in DB has well over a million rows, Table #T2 has 100 rows. Both tables are in Column format and I need to Pivot to rows and join both.
Can I get it all in one query with Cross Apply and remove the cte?
This is my code, I have correct output but is this the most efficient way to do this considering number of rows?
with cte_sizes
as
(
select SizeRange,Size,ColumnPosition
from #T2
cross apply (
values(Sz1,1),(Sz2,2),(Sz3,3),(Sz4,4)
) X (Size,ColumnPosition)
)
select a.ProductID,a.SizeRange,c.Size,isnull(x.Qty,0) as Qty
from #T1 a
cross apply (
values(a.Sale1,1),(a.Sale2,2),(a.Sale3,3),(a.Sale4,4)
) X (Qty,ColumnPosition)
inner join cte_sizes c
on c.SizeRange = a.SizeRange
and c.ColumnPosition = x.ColumnPosition
I have also code and considered this but is this the CROSS APPLY a better method?
with cte_sizes
as
(
select 1 as SizePos
union all
select SizePos + 1 as SizePos
from cte_sizes
where SizePos < 4
)
select a.ProductID
,a.SizeRange
,(case when b.SizePos = 1 then c.Sz1
when b.SizePos = 2 then c.Sz2
when b.SizePos = 3 then c.Sz3
when b.SizePos = 4 then c.Sz4 end
) as Size
,isnull((case when b.SizePos = 1 then a.Sale1
when b.SizePos = 2 then a.Sale2
when b.SizePos = 3 then a.Sale3
when b.SizePos = 4 then a.Sale4 end
),0) as Qty
from #T1 a
inner join #T2 c on c.SizeRange = a.SizeRange
cross join cte_sizes b
This is wild guessing, but my magic crystall ball told me, that you might be looking for something like this:
For this we do not need your table #TS at all.
WITH Unpivoted2 AS
(
SELECT t2.SizeRange,A.* FROM #t2 t2
CROSS APPLY(VALUES(1,t2.Sz1)
,(2,t2.Sz2)
,(3,t2.Sz3)
,(4,t2.Sz4)) A(SizePos,Size)
)
SELECT t1.ProductID
,Unpivoted2.SizeRange
,Unpivoted2.Size
,Unpivoted1.Qty
FROM #t1 t1
CROSS APPLY(VALUES(1,t1.Sale1)
,(2,t1.Sale2)
,(3,t1.Sale3)
,(4,t1.Sale4)) Unpivoted1(SizePos,Qty)
LEFT JOIN Unpivoted2 ON Unpivoted1.SizePos=Unpivoted2.SizePos AND t1.SizeRange=Unpivoted2.SizeRange
ORDER BY t1.ProductID,Unpivoted2.SizeRange;
The result:
+-----------+-----------+------+------+
| ProductID | SizeRange | Size | Qty |
+-----------+-----------+------+------+
| 123 | S-XL | S | 1 |
+-----------+-----------+------+------+
| 123 | S-XL | M | 12 |
+-----------+-----------+------+------+
| 123 | S-XL | L | 13 |
+-----------+-----------+------+------+
| 123 | S-XL | XL | 14 |
+-----------+-----------+------+------+
| 456 | 8-14 | 8 | 2 |
+-----------+-----------+------+------+
| 456 | 8-14 | 10 | 22 |
+-----------+-----------+------+------+
| 456 | 8-14 | 12 | NULL |
+-----------+-----------+------+------+
| 456 | 8-14 | 14 | 24 |
+-----------+-----------+------+------+
| 789 | S-L | S | 3 |
+-----------+-----------+------+------+
| 789 | S-L | M | NULL |
+-----------+-----------+------+------+
| 789 | S-L | L | 33 |
+-----------+-----------+------+------+
| 789 | S-L | XL | NULL |
+-----------+-----------+------+------+
The idea in short:
The cte will return your #T2 in an unpivoted structure. Each name-numbered column (something you should avoid) is return as a single row with an index indicating the position.
The SELECT will do the same with #T1 and join the cte against this set.
UPDATE: After a lot of comments...
If I get this (and the changes to the initial question) correctly, the approach above works perfectly well, but you want to know, what was best in performance.
The first answer to "What is the fastest approach?" is Race your horses by Eric Lippert.
Good to know 1: A CTE is nothing more then syntactic sugar. It will allow to type a sub-query once and use it like a table, but it has no effect to the way how the engine will work this down.
Good to know 2: It is a huge difference whether you use APPLY or JOIN. The first will call the sub-source once per row, using the current row's values. The second will have to create two sets first and will then join them by some condition. There is no general "what is better"...
For your issue: As there is one very big set and one very small set, all depends on when you reduce the big set usig any kind of filter. The earlier the better.
And most important: It is - in any case - a sign of bad structures - when you find name numbering (something like phone1, phone2, phoneX). The most expensive work will be to transform your 4 name-numbered columns to some dedicated rows. This should be stored in normalized format...
If you still need help, I'd ask you to start a new question.

SQL union / join / intersect multiple select statements

I have two select statements. One gets a list (if any) of logged voltage data in the past 60 seconds and related chamber names, and one gets a list (if any) of logged arc event data in the past 5 minutes. I am trying to append the arc count data as new columns to the voltage data table. I cannot figure out how to do this.
Note that, there may or may not be arc count rows, for a given chamber name that is in the voltage data table. If there are no rows, I want to set the arc count column value to zero.
Any ideas on how to accomplish this?
Voltage Data:
SELECT DISTINCT dbo.CoatingChambers.Name,
AVG(dbo.CoatingGridVoltage_Data.ChanA_DCVolts) AS ChanADC,
AVG(dbo.CoatingGridVoltage_Data.ChanB_DCVolts) AS ChanBDC,
AVG(dbo.CoatingGridVoltage_Data.ChanA_RFVolts) AS ChanARF,
AVG(dbo.CoatingGridVoltage_Data.ChanB_RFVolts) AS ChanBRF FROM
dbo.CoatingGridVoltage_Data LEFT OUTER JOIN dbo.CoatingChambers ON
dbo.CoatingGridVoltage_Data.CoatingChambersID =
dbo.CoatingChambers.CoatingChambersID WHERE
(dbo.CoatingGridVoltage_Data.DT > DATEADD(second, - 60,
SYSUTCDATETIME())) GROUP BY dbo.CoatingChambers.Name
Returns
Name | ChanADC | ChanBDC | ChanARF | ChanBRF
-----+-------------------+--------------------+---------------------+------------------
OX2 | 2.9099999666214 | -0.485000004371007 | 0.344801843166351 | 0.49748428662618
S2 | 0.100000001490116 | -0.800000016887983 | 0.00690172302226226 | 0.700591623783112
S3 | 4.25666658083598 | 0.5 | 0.96554297208786 | 0.134956782062848
Arc count table:
SELECT CoatingChambers.Name,
SUM(ArcCount) as ArcCount
FROM CoatingChambers
LEFT JOIN CoatingArc_Data
ON dbo.[CoatingArc_Data].CoatingChambersID = dbo.CoatingChambers.CoatingChambersID
where EventDT > DATEADD(mi,-5, GETDATE())
Group by Name
Returns
Name | ArcCount
-----+---------
L1 | 283
L4 | 0
L6 | 1
S2 | 55
To be clear, I want this table (with added arc count column), given the two tables above:
Name | ChanADC | ChanBDC | ChanARF | ChanBRF | ArcCount
-----+-------------------+--------------------+---------------------+-------------------+---------
OX2 | 2.9099999666214 | -0.485000004371007 | 0.344801843166351 | 0.49748428662618 | 0
S2 | 0.100000001490116 | -0.800000016887983 | 0.00690172302226226 | 0.700591623783112 | 55
S3 | 4.25666658083598 | 0.5 | 0.96554297208786 | 0.134956782062848 | 0
You can treat the select statements as virtual tables and just join them together:
select
x.Name,
x.ChanADC,
x.ChanBDC,
x.ChanARF,
x.ChanBRF,
isnull( y.ArcCount, 0 ) ArcCount
from
(
select distinct
cc.Name,
AVG(cgv.ChanA_DCVolts) AS ChanADC,
AVG(cgv.ChanB_DCVolts) AS ChanBDC,
AVG(cgv.ChanA_RFVolts) AS ChanARF,
AVG(cgv.ChanB_RFVolts) AS ChanBRF
from
dbo.CoatingGridVoltage_Data cgv
left outer join
dbo.CoatingChambers cc
on
cgv.CoatingChambersID = cc.CoatingChambersID
where
cgv.DT > dateadd(second, - 60, sysutcdatetime())
group by
cc.Name
) as x
left outer join
(
select
cc.Name,
sum(ac.ArcCount) as ArcCount
from
dbo.CoatingChambers cc
left outer join
dbo.CoatingArc_Data ac
on
ac.CoatingChambersID = cc.CoatingChambersID
where
EventDT > dateadd(mi,-5, getdate())
group by
Name
) as y
on
x.Name = y.Name
Also, it's worthwhile to simplify your names with aliases and format the queries for readability...which I shamelessly took a stab at.

Multiple select or distinct

I am new to sql so looking for a little help - got the first part down however I am having issues with the second part.
I got the three tables tied together. First I needed to tie tblPatient.ID = tblPatientVisit.PatientID together to eliminate dups which works
Now I need to take those results and eliminate dups in the MRN but my query is only returning one result which is WRONG - LOL
Query
select
tblPatient.id,
tblPatient.firstname,
tblPatient.lastname,
tblPatient.dob,
tblPatient.mrn,
tblPatientSmokingScreenOrder.SmokeStatus,
tblPatientVisit.VisitNo
from
tblPatient,
tblPatientSmokingScreenOrder,
tblPatientVisit
Where
tblPatient.ID = tblPatientVisit.PatientID
and tblPatientVisit.ID = tblPatientSmokingScreenOrder.VisitID
and tblPatient.ID in(
Select Distinct
tblPatient.mrn
From
tblPatient
where
isdate(DOB) = 1
and Convert(date,DOB) <'12/10/2000'
and tblPatientVisit.PatientType = 'I')
Actual Results:
ID | firstName | LastName | DOB | MRN | SmokeStatus | VisitNO
12 | Test Guy | Today | 12/12/1023 | 0015396 | Never Smoker | 0013957431
Desired Results:
90 | BOB | BUILDER | 02/24/1974 | 0015476 | Former Smoker | 0015476001
77 | DORA | EXPLORER | 06/04/1929 | 0015463 | Never Smoker | 0015463001
76 | MELODY | VALENTINE | 09/17/1954 | 0015461 | Current | 0015461001
32 | STRAWBERRY | SHORTCAKE | 07/06/1945 | 0015415 | Current | 0015415001
32 | STRAWBERRY | SHORTCAKE | 07/06/1945 | 0015415 | Never Smoker | 0015415001
32 | STRAWBERRY | SHORTCAKE | 07/06/1945 | 0015415 | Former Smoker | 0015415001
12 | Test Guy | Today | 12/12/1023 | 0015345 | Never Smoker | 0013957431
Anyone have any suggestions on how I go down to the next level and get all the rows with one unique MRN. From the data above I should have 5 in my list. Any help would be appreciated.
Thanks
If I had to guess -- The only thing that looks odd (but maybe it's OK) is that you're comparing patient.ID from your parent query to patient.mrn in the subquery.
Beyond that -- things to check:
(1)
Do you get all your patients with the inner query?
Select Distinct
tblPatient.mrn
From
tblPatient
where
isdate(DOB) = 1
and Convert(date,DOB) <'12/10/2000'
and tblPatientVisit.PatientType = 'I'
(2)
What is your patient type for the missing records? (Your filtering it to tblPatientVisit.PatientType = 'I' -- do the missing records have that patient type as well?)
Perhaps you need to invert the logic here. As in,
Select Distinct
patients.mrn1
From (select
tblPatient.id as id1,
tblPatient.firstname as firstname1,
tblPatient.lastname as lastname1,
tblPatient.dob as DOB1,
tblPatient.mrn as mrn1,
tblPatientSmokingScreenOrder.SmokeStatus as SmokeStatus1,
tblPatientVisit.VisitNo as VisitNo1,
tblPatientVisit.PatientType as PatientType1,
from
tblPatient,
tblPatientSmokingScreenOrder,
tblPatientVisit
Where
tblPatient.ID = tblPatientVisit.PatientID
and tblPatientVisit.ID = tblPatientSmokingScreenOrder.VisitID
) as patients
where
isdate(patients.DOB1) = 1
and Convert(date,patients.DOB1) <'12/10/2000'
and patients.PatientType1 = 'I');
Cleaned it up a bit and I think they were right. MRN wont match patient id, at least not from your example data. You should not need an inner query. This query should give you what you want.
SELECT DISTINCT
p.id,
p.firstname,
p.lastname,
p.dob,
p.mrn,
s.SmokeStatus,
v.VisitNo
FROM
tblPatient p
JOIN tblPatientVisit v ON p.id = v.patientId
JOIN tblPatientSmokingScreenOrder s ON v.id = s.visitId
WHERE
isdate(p.DOB) = 1
AND CONVERT(date,p.DOB) <'12/10/2000'
AND v.PatientType = 'I'