MSSQL/TSQL separating fields into rows based on value - sql

I have two tables with data like:
table: test_results
ID |test_id |test_type |result_1 |amps |volts |power |
----+-----------+-----------+-----------+-----------+-----------+-----------+
1 |101 |static |10.1 |5.9 |15 |59.1 |
2 |101 |dynamic |300.5 |9.1 |10 |40.1 |
3 |101 |prime |48.9 |8.2 |14 |49.2 |
4 |101 |dual |235.2 |2.9 |11 |25.8 |
5 |101 |static |11.9 |4.3 |9 |43.3 |
6 |101 |prime |49.9 |5.8 |15 |51.6 |
and
table: test_records
ID |model |test_date |operator |
----+-----------+-----------+-----------+
101 |m-300 |some_date |john doe |
102 |m-243 |some_date |john doe |
103 |m-007 |some_date |john doe |
104 |m-523 |some_date |john doe |
105 |m-842 |some_date |john doe |
106 |m-252 |some_date |john doe |
and I'm making a report that looks like this:
|static |dynamic |
test_id |model |test_date |operator |result_1 |amps |volts |power |result_1 |amps |volts |power |
-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+-----------+
101 |m-300 |some_date |john doe |10.1 |5.9 |15 |59.1 |300.5 |9.1 |10 |40.1 |
with left outer joins like so:
SELECT
A.ID AS test_id, model, test_date, operator,
B.result_1, B.amps, B.volts, B.power,
C.result_1, C.amps, C.volts, C.power
FROM
test_records A
LEFT JOIN
test_results B
ON
A.ID = B.test_id
AND
B.test_type = 'static'
LEFT JOIN
test_results C
ON
A.ID = C.test_id
AND
C.test_type = 'dynamic'
But I have run into a problem. The "static" and "prime" tests are run twice.
I don't know how to differentiate between them to create their own 4 fields.
An abstracted(simplified) view of the desired report would look like:
|static |dynamic |prime |dual |static2 |prime2 |
|4 fields |4 fields |4 fields |4 fields |4 fields |4 fields |
Is this even possible?
Notes:
I'm labeling the groups of 4 fields with html so don't worry about the labels
Not every test will run "static" and "prime" twice. So this is a case of If ("static" and "prime") are found twice, do this SQL.
I think we're going to get our engineers to append a 2 to the second tests, eliminating the problem, so this question is more out of curiosity to know what method could solve a problem like this.

If you have another field (here I use ID) that you know is always going to be ordered in respect to the field you can use a windowing function to give them sequential values and then join to that. Like this:
WITH test_records_numbered AS
(
SELECT test_id, test_type, result_1, amps, volts, power,
ROW_NUMBER() OVER (PARTITION BY test_id, test_type ORDER BY ID) as type_num
FROM test_records
)
SELECT
A.ID AS test_id, model, test_date, operator,
B.result_1, B.amps, B.volts, B.power,
C.result_1, C.amps, C.volts, C.power
FROM test_records A
LEFT JOIN test_records_numbered B
ON A.ID = B.test_id AND B.test_type = 'static' and B.type_num = 1
LEFT JOIN test_records_numbered C
ON A.ID = C.test_id AND C.test_type = 'dynamic' and C.type_num = 2
I use a CTE to make it clearer but you could use a sub-queries, you would (of course) have to have the same sub-query twice in the SQL, most servers would have no issue optimizing without the CTE I expect.
I feel this solution is a bit of a "hack." You really want your original data to have all the information it needs. So I think it is good you are having your app developers modify their code (FWIW).
If this had to go into production I think I would break out the numbering as a view to hi-light the codification of questionable business rules (and to make it easy to change)

Related

How can I apply mutliple filters on my sql tables

I am struggling with what looks to be relational division in sql. I need to filter data from the same table based on multiple criteria. Here-under is a schema of what my table looks like.
| -------|----------------|-----------------|----------------|
|ID |Question |RespondentId |Answer |
| -------|----------------|-----------------|----------------|
|1 |Big |1 |Yes |
|2 |Big |2 |Yes |
|3 |Big |3 |No |
|4 |Gender |1 |Male |
|5 |Gender |2 |Female |
|6 |Gender |3 |Female |
|7 |Children |1 |No |
|8 |Children |2 |Yes |
|9 |Children |3 |No |
--------------------------------------------------------------
I need the RespondenIds from this table called Answers that match the following filters : Question = Big and Children and Answer = Yes and Yes respectively for every question. Therefore, if I would have a correct Sql query my result should return me the following array : [2] since the only row that has the answer Yes for the question Big and the answer Yes for the question Children is the one with RespondentId = 2.
Also, the questions and the answers provided are not fixed and should be modular. For instance, I should be able to change an answer or removing a question without having to change the whole structure of my query.
Could you please help me finding a correct query for this problem ? I have been looking to a lot of explanations provided by #Erwin Brandstetter but none of them match my needs.
I would do this as:
select a.RespondentId
from Answers a
when (question, answer) in ( ('Big', 'Yes'), ('Children', 'Yes') )
group by RespondentId
having count(*) = 2 ;
This is easily generalized to:
with qa as (
select v.*
from (values ('Big', 'Yes'), ('Children', 'Yes')
) v(question, answer)
select a.RespondentId
from Answers a join
qa
on a.question = qa.question and a.answer = qa.answer
group by RespondentId
having count(*) = (select count(*) from qa);
This is pretty generalizable. You could even arrange the CTE to take an array or json argument and parse out into the separate comparison values.
You could check for result that having a count = 2 for the rows that match the question and answer where condition
select RespondentId
from Answers
when question in ( 'Big', 'Children')
and Answer ='Yes'
group by RespondentId having count(*) = 2
I think what you are looking for is to Pivot the table. There are different syntaxes for different databases. You effectively turn the values of the "Question" column into columns of their own and then look for the rows matching your criteria.
Here is an inefficient example in standard SQL where I create one table for each question and join them into a single table using the RespondentId.
select respondent_id from
(select * from answers where question = 'Big') as big
join
(select * from answers where question = 'Children') as children
on
big.respondent_id = children.respondent_id
where
big.answer = 'Yes'
and
children.answer = 'Yes';

Select rows that are duplicates on two columns

I have data in a table. There are 3 columns (ID, Interval, ContactInfo). This table lists all phone contacts. I'm attempting to get a count of phone numbers that called twice on the same day and have no idea how to go about this. I can get duplicate entries for the same number but it does not match on date. The code I have so far is below.
SELECT ContactInfo, COUNT(Interval) AS NumCalls
FROM AllCalls
GROUP BY ContactInfo
HAVING COUNT(AllCalls.ContactInfo) > 1
I'd like to have it return the date, the number of calls on that date if more than 1, and the phone number.
Sample data:
|ID |Interval |ContactInfo|
|--------|------------|-----------|
|1 |3/1/2017 |8009999999 |
|2 |3/1/2017 |8009999999 |
|3 |3/2/2017 |8001234567 |
|4 |3/2/2017 |8009999999 |
|5 |3/3/2017 |8007771111 |
|6 |3/3/2017 |8007771111 |
|--------|------------|-----------|
Expected result:
|Interval |ContactInfo|NumCalls|
|------------|-----------|--------|
|3/1/2017 |8009999999 |2 |
|3/3/2017 |8007771111 |2 |
|------------|-----------|--------|
Just as juergen d suggested, you should try to add Interval in your GROUP BY. Like so:
SELECT AC.ContactInfo
, AC.Interval
, COUNT(*) AS qnty
FROM AllCalls AS AC
GROUP BY AC.ContactInfo
, AC.Interval
HAVING COUNT(*) > 1
The code should like this :
select Interval , ContactInfo, count(ID) AS NumCalls from AllCalls group by Interval, ContactInfo having count(ID)>1;

Fetch data from multiple tables in postgresql

I am working on an application where I want to fetch the records from multiple tables which are connected through foreign key. The query I am using is
select ue.institute, ue.marks, uf.relation, uf.name
from user_education ue, user_family uf where ue.user_id=12 and uf.user_id=12
The result of the query is
You can see the data is repeating in it. I only want a record one time. I want no repetition. I want something like this
T1 T2
id|name|fid id|descrip| fid
1 |A |1 1|DA | 1
2 |B |1 2|DB | 1
2 |B |1
Result which I want:
Result:
id|name|fid|id|descrip| fid
1 |A |1 |1|DA | 1
2 |B |1 |2|DB | 1
2 |B |1 |
The results fetched through your query
The total rows are 5
More Information
I want the rows of same user_id from both tables but you can see in T1 there are 3 rows and in T2 there are 2 rows. I do not want repetitions but also I want to fetch all the data on the basis of user_id
Table Schemas,s
T1
T2
I can't see why you would want that, but the solution could be to use the window function row_number():
SELECT ue.institute, ue.marks, uf.relation, uf.name
FROM (SELECT institute, marks, row_number() OVER ()
FROM user_education
WHERE user_id=12) ue
FULL OUTER JOIN
(SELECT relation, name, row_number() OVER ()
FROM user_family
WHERE user_id=12) uf
USING (row_number);
The result would be pretty meaningless though, as there is no ordering defined in the individual result sets.

SQL Join through Mappings Table

I may have made the datascheme more complicated than i need to however this is the current data scheem and what im trying got accomplish.
Actor
id|name |website |...
1 |james |www.james.com |...
2 |ben |www.ben.com |...
Movie
id|name |poster|runtime...
1 |titanic |/a.jpg|98|
2 |Terminator|/b.jpg|77|
3 |MIB |/c.jpg|89|
Character
id|name
1 |tony
2 |bilbo
3 |gandalf
ActorMovieCharacterMapping
id|movie_id|actor_id|character_id
1 | 3 |5 |3
2 | 2 |5 |2
3 | 3 |2 |6
My goal is to get all of the Actors with their character name for a given movie in the fewest number of SQL queries as possible. I can get all of the actors in a movie or characters in a movie but dont know how to also retrieve that actors character name for that movie. Any help would be appreciated as well as possible ways to format these tables better.
Just do joins on mapping table:
select a.name as actor_name, c.name as character_nane
from ActorMovieCharacterMapping m
join Actor a on m.actor_id = a.id
join Movie mo on m.monie_id = mo.id
join Character c on m.character_id = c.id
where mo.id = 1

sum rows from one table and move it to another table

How can I sum rows from one table (based on selected critiria) and move the outcome to another table.
I have a table related to costs within project:
Table "costs":
id| CostName |ID_CostCategory| PlanValue|DoneValue
-------------------------------------------------------
1 | books |1 |100 |120
2 | flowers |1 |90 |90
3 | car |2 |150 |130
4 | gas |2 |50 |45
and I want to put the sum of "DoneValue" of each ID_CostCategory into table "CostCategories"
Table "CostCategories":
id|name |planned|done
------------------------
1 |other|190 |takes the sum from above table
2 |car |200 |takes the sum from above table
Many thanks
I would not store this, because as soon as anything changes in Costs then CostCategories will be out of date, instead I would create a view e.g:
CREATE VIEW CostCategoriesSum
AS
SELECT CostCategories.ID,
CostCategories.Name,
SUM(COALESCE(Costs.PlanValue, 0)) AS Planned,
SUM(COALESCE(Costs.DoneValue, 0)) AS Done
FROM CostCategories
LEFT JOIN Costs
ON Costs.ID_CostCategory = CostCategories.ID
GROUP BY CostCategories.ID, CostCategories.Name;
Now instead of referring to the table, you can refer to the view and the Planned and Done totals will always be up to date.
INSERT INTO CostCategories(id,name,planned,done)
SELECT ID_CostCategory, CostName, SUM(PlanValue), SUM(DoneValue)
FROM costs GROUP BY ID_CostCategory, CostName