Set multiple column values from previous row using Postgres SQL - sql

Assume you have a series of responses from people on what their favorite colors are. This information is stored in a SQL table:
| id | favorite_color | friend_recommendation_id |
|----|----------------|--------------------------|
| 1 | green | |
| 2 | blue | |
| 3 | yellow | |
| 4 | green | |
| 5 | yellow | |
| 6 | green | |
My goal is to write a Postgres SQL query that would fill the friend_recommendation column with the id of the most recent person to respond with the same color as the provided individual. This would result in the following table:
| id | favorite_color | friend_recommendation_id |
|----|----------------|--------------------------|
| 1 | green | |
| 2 | blue | |
| 3 | yellow | |
| 4 | green | 1 |
| 5 | yellow | 3 |
| 6 | green | 4 |
Note that id 6 is filled with 4 and not 1
I've tried using variables and subselects, but am struggling with how to apply the select for each result from the parent query.

Use a subquery to calculate the field
SQL Fiddle Demo
SELECT "id", "favorite_color", (SELECT MAX("id")
FROM colors c2
WHERE c2."favorite_color" = c1."favorite_color"
AND c2."id" < c1."id"
) as friend_recommendation_id
FROM colors c1
OUTPUT
| id | favorite_color | friend_recommendation_id |
|----|----------------|--------------------------|
| 1 | green | (null) |
| 2 | blue | (null) |
| 3 | yellow | (null) |
| 4 | green | 1 |
| 5 | yellow | 3 |
| 6 | green | 4 |
Can also be write like this:
SELECT c1."id", c1."favorite_color", MAX(c2."id") as friend_recommendation_id
FROM colors c1
LEFT JOIN colors c2
ON c2."favorite_color" = c1."favorite_color"
AND c2."id" < c1."id"
GROUP BY c1."id", c1."favorite_color"
ORDER BY c1."id";
UPDATE
UPDATE colors target
SET "friend_recomendation_id" = ( SELECT MAX("id")
FROM colors c2
WHERE c2."favorite_color" = target."favorite_color"
AND c2."id" < target."id")

Related

SQL join + group_concat not returning some rows

In my database, i have the following tables:
People
+-----------+------------+
| IdPeople | Name |
+-----------+------------+
| 1 | James |
| 2 | Chris |
+-----------+------------+
ref
+---------+-------------+------+
| People | Color | Code |
+---------+-------------+------+
| 1 | 2 | 1 |
| 1 | 1 | 2 |
| 1 | 6 | 3 |
| 2 | 1 | 1 |
| 2 | 6 | 4 |
| 2 | 4 | NULL |
| 2 | 5 | NULL |
+---------+-------------+------+
Colors
+--------+--------------------+
| IdCol | Color |
+--------+--------------------+
| 1 | Blue |
| 2 | Green |
| 3 | Yellow |
| 4 | Red |
| 5 | Black |
| 6 | White |
+--------+--------------------+
Codes
+--------+----------------+
| IdCode | Code |
+--------+----------------+
| 1 | C++ |
| 2 | JavaScript |
| 3 | Python |
| 4 | HTML |
+--------+----------------+
I want to join all the tables to get something like this:
+------------+---------------------------+------------------------------------+
| Name | Color | Code |
+------------+---------------------------+------------------------------------+
| Chris | Blue, White, Red, Black | C++, HTML |
| James | Green, Blue, White | C++, JavaScript, Python |
+------------+---------------------------+------------------------------------+
I tried to join with group_concat like this
SELECT People.Name,
group_concat(Colors.Color separator ", ") AS "Color",
group_concat(Codes.Code separator ", ") AS "Code"
FROM People
INNER JOIN ref ON People.IdPeople=ref.People
INNER JOIN Colors ON ref.Color=Colors.IdCol
INNER JOIN Codes ON ref.Code=Codes.Code
GROUP BY Name;
However, since group_concat doesn't return empty lines, i get everything except Black and White in the Chris line. I don't know how to do this, so can someone help me please?
As ref.code can be null, you must outer join the code table: LEFT OUTER JOIN Codes ON ....
The full query:
SELECT People.Name,
group_concat(Colors.Color separator ", ") AS "Color",
group_concat(Codes.Code separator ", ") AS "Code"
FROM People
INNER JOIN REF ON People.IdPeople=ref.People
INNER JOIN Colors ON ref.Color=Colors.IdCol
LEFT JOIN Codes ON ref.Code=Codes.Code
GROUP BY Name;

Is there a way to alternate the order of a result based off one id while keeping groupings based off another id in SQL?

I have this query in Postgresql:
(SELECT q.question, q.category_id, a.id, a.question_id, a.answer
FROM questions q, answers a
WHERE q.id = a.question_id
AND category_id = 1
AND question_id
BETWEEN (SELECT property FROM users WHERE email = 'test#test.com')
AND (SELECT property FROM users WHERE email = 'test#test.com') + 14)
UNION
(SELECT q.question, q.category_id, a.id, a.question_id, a.answer
FROM questions q, answers a
WHERE q.id = a.question_id
AND category_id = 2
AND question_id
BETWEEN (SELECT laws FROM users WHERE email = 'test#test.com')
AND (SELECT laws FROM users WHERE email = 'test#test.com') + 16)
ORDER BY question_id, id
that currently returns results in this format:
+-----------------------------+-------------+-------------+--------+
| question | category_id | question_id | answer |
+-----------------------------+-------------+-------------+--------+
| What color is the sky? | 1 | 16 | blue |
| What color is the sky? | 1 | 16 | green |
| What color is the sky? | 1 | 16 | purple |
| What color is the sky? | 1 | 16 | red |
| What color is a firetruck? | 1 | 17 | orange |
| What color is a firetruck? | 1 | 17 | teal |
| What color is a firetruck? | 1 | 17 | red |
| What color is a firetruck? | 1 | 17 | green |
| What color is dirt? | 2 | 18 | green |
| What color is dirt? | 2 | 18 | green |
| What color is dirt? | 2 | 18 | green |
| What color is dirt? | 2 | 18 | green |
+-----------------------------+-------------+-------------+--------+
What I want to do is alternate the order based off the category_id, so the category id would be alternating like this: 1,2,1,2, but I want to keep the groups based off the question_id. So the result would look like this:
+-----------------------------+-------------+-------------+--------+
| question | category_id | question_id | answer |
+-----------------------------+-------------+-------------+--------+
| What color is the sky? | 1 | 16 | blue |
| What color is the sky? | 1 | 16 | green |
| What color is the sky? | 1 | 16 | purple |
| What color is the sky? | 1 | 16 | red |
| What color is dirt? | 2 | 18 | green |
| What color is dirt? | 2 | 18 | green |
| What color is dirt? | 2 | 18 | green |
| What color is dirt? | 2 | 18 | green |
| What color is a firetruck? | 1 | 17 | orange |
| What color is a firetruck? | 1 | 17 | teal |
| What color is a firetruck? | 1 | 17 | red |
| What color is a firetruck? | 1 | 17 | green |
+-----------------------------+-------------+-------------+--------+
I've tried to using ORDER BY row_number() OVER (PARTITION BY t.category_id ORDER BY t.category_id)
but that just results in each being alternated without being grouped by question_id
I think you want the following order by clause:
order by
rank() over(partition by category_id order by question_id),
question_id,
id
Basically this interleaves the categories/questions tuples.
Notes:
use standard, explicit joins (from ... join ... on) rather than old-school, implicit joins (from ..., ... where ...); this is prehistoric syntax, that should not be used in new code
it is rather likely that your query could be simplified to not use union; if you were to ask another question with sample data, and desired results, one might be able to suggest

Update where value pair matches in SQL

I need to update this table:
Centers:
+-----+------------+---------+--------+
| id | country | process | center |
+-----+------------+---------+--------+
| 1 | 1 | 1 | 1 |
| 2 | 1 | 2 | 1 |
| 3 | 1 | 3 | 1 |
| 4 | 2 | 1 | 1 |
| 5 | 2 | 2 | 1 |
| 6 | 2 | 3 | 1 |
| 7 | 3 | 1 | 1 |
| 8 | 3 | 2 | 1 |
| 9 | 3 | 3 | 1 |
+-----+------------+---------+--------+
During a selection process I retrieve two tempTables:
TempCountries:
+-----+------------+
| id | country |
+-----+------------+
| 1 | 1 |
| 2 | 3 |
+-----+------------+
And TempProcesses:
+-----+------------+
| id | process |
+-----+------------+
| 1 | 2 |
| 2 | 3 |
+-----+------------+
In a subquery I get all possible combinations of the values:
SELECT TempCountries.countryId, TempProcesses.processesId FROM TempCenterCountries,TempCenterProcesses
This returns:
+-----+------------+---------+
| id | country | process |
+-----+------------+---------+
| 1 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 3 | 2 |
| 4 | 3 | 3 |
+-----+------------+---------+
During the selection process the user chooses a center for these combinations. Let’s say center = 7.
Now I need to update the center value in the Centers table where the combinations of the subquery are present.
So,
UPDATE Centers SET center = 7 WHERE ?
So I get:
+-----+------------+---------+--------+
| id | country | process | center |
+-----+------------+---------+--------+
| 1 | 1 | 1 | 1 |
| 2 | 1 | 2 | 7 |
| 3 | 1 | 3 | 7 |
| 4 | 2 | 1 | 1 |
| 5 | 2 | 2 | 1 |
| 6 | 2 | 3 | 1 |
| 7 | 3 | 1 | 1 |
| 8 | 3 | 2 | 7 |
| 9 | 3 | 3 | 7 |
+-----+------------+---------+--------+
Not all sql implementations let you have a from clause when using update. Fortunately in your case since you're doing a Cartesian product to get all the combinations it implies that you don't have any constraints between the two values.
UPDATE Centers
SET center = 7
WHERE country IN (SELECT countryId FROM TempCountries)
AND process IN (SELECT processId FROM TempCenterProcesses)
Try if this standard sql,
Update Centers
set center = 7
where country in (select country from TempCenterCountries)
and process in (select process from TempCenterProcesses)
You need to have exact match of country as well as process before you run the update query. So, something like below query would help you achieve that. Basically update the column if there exists a record
WITH (SELECT TempCountries.countryId, TempProcesses.processesId
FROM TempCenterCountries,
TempCenterProcesses) AS TempTables,
UPDATE Centers
SET center = 7
WHERE EXISTS (SELECT 1
FROM TempTables tmp
WHERE country = tmp.countryId and process = tmp.processesId
);
The idea is to update the record if both country and process matches with the one you have already fetched in temporary table.
Use update join -
For Sql Server
update c set SET center = 7 from Centers c
join
(SELECT TempCountries.countryId, TempProcesses.processesId FROM TempCenterCountries join TempCenterProcesses
)A on c.countryid=A.countryid and c.processesId=A.processId
For Mysql -
update Centers c
join
(SELECT TempCountries.countryId, TempProcesses.processesId FROM TempCenterCountries join TempCenterProcesses
)A on c.countryid=A.countryid and c.processesId=A.processId
set SET center = 7

MODE in Teradata SQL - excluding a value from the range, and using multiple tables

I am comparing 3 tables of data for what should be the same demands, and want to create a table that shows the MODE from two of the tables (in order to make a suggestion of what the correct value could be). I may need to concatenate as I am looking for the mode of all rows with the same ID and Name.
Table 1:
+----------+----------+------+
| DemandNo | Forename | Size |
+----------+----------+------+
| 1 | Richard | 42 |
| 2 | Richard | 42 |
| 3 | Richard | 42 |
| 4 | Richard | 36 |
| 5 | Richard | 36 |
| 6 | Richard | 36 |
| 7 | Richard | 36 |
| 8 | Luke | 14 |
| 9 | Luke | 14 |
| 10 | Luke | 14 |
| 11 | Luke | 14 |
| 12 | Luke | 14 |
| 13 | Luke | 25 |
| 14 | Luke | 25 |
| 15 | Luke | 25 |
+----------+----------+------+
Table 2:
+----------------+-----------------+
| List1_DemandNo | List1_PenColour |
+----------------+-----------------+
| 1 | White |
| 2 | Black |
| 3 | Black |
| 4 | Red |
| 5 | ? |
| 6 | Red |
| 7 | Red |
| 8 | Yellow |
| 9 | Yellow |
| 10 | Yellow |
| 11 | Green |
| 12 | Yellow |
| 13 | Green |
| 14 | ? |
| 15 | ? |
+----------------+-----------------+
Table 3:
+----------------+-----------------+
| List2_DemandNo | List2_PenColour |
+----------------+-----------------+
| 1 | White |
| 2 | Green |
| 3 | Green |
| 4 | Red |
| 5 | ? |
| 6 | Red |
| 7 | Red |
| 8 | Pink |
| 9 | Pink |
| 10 | Yellow |
| 11 | Green |
| 12 | Pink |
| 13 | Orange |
| 14 | Orange |
| 15 | Orange |
+----------------+-----------------+
So I need to generate a recommendation for each person with the same name and size. The recommendation should be the MODE of all rows in table1 where the person has the same Forename and Size (do I need to concatenate Forename and size?)
The other requirement is that all question marks "?" should be excluded from the MODE/recommendation.
So my results table should look something like this:
+----------+----------+-----+------------+------------+
| DemandNo | Forename | Size | List1_MODE | List2_MODE |
+----------+----------+-----+------------+------------+
| 1 | Richard | 42 | Black | Green |
| 2 | Richard | 42 | Black | Green |
| 3 | Richard | 42 | Black | Green |
| 4 | Richard | 36 | Red | Red |
| 5 | Richard | 36 | Red | Red |
| 6 | Richard | 36 | Red | Red |
| 7 | Richard | 36 | Red | Red |
| 8 | Luke | 14 | Yellow | Pink |
| 9 | Luke | 14 | Yellow | Pink |
| 10 | Luke | 14 | Yellow | Pink |
| 11 | Luke | 14 | Yellow | Pink |
| 12 | Luke | 14 | Yellow | Pink |
| 13 | Luke | 25 | Green | Orange |
| 14 | Luke | 25 | Green | Orange |
| 15 | Luke | 25 | Green | Orange |
+----------+----------+-----+------------+------------+
I understand that the MODE function does not work in Teradata, and that I would need to perform a count, but the complexity of calculating the mode using 3 tables and excluding the ? is sadly beyond my SQL skills -Any help would be truly appreciated!
Many thanks
Richard
You need to write a separate select for each mode and join to it:
select t1.*, List1_PenColour, List2_PenColour
from table1 as t1
left join
(
select Forename, Size, List1_PenColour
from table1 as t1
join table2 as t2
on t1.DemandNo = t2.List1_DemandNo
and List1_PenColour <> '?'
group by Forename, Size, List1_PenColour
-- return only the row with the highest count
-- random row if multiple rows with the same count exist
qualify row_number()
over (partiton by Forename, Size, List1_PenColour
order by count(*) desc) = 1
) as list1
on t1.Forename = list1.Forename
and t1.Size = list1.Size
left join
(
select Forename, Size, List2_PenColour
from table1 as t1
join table3 as t3
on t1.DemandNo = t3.List2_DemandNo
and List2_PenColour <> '?'
group by Forename, Size, List2_PenColour
qualify row_number()
over (partiton by Forename, Size, List2_PenColour
order by count(*) desc) = 1
) as list2
on t1.Forename = list2.Forename
and t1.Size = list2.Size

Query to subtract all numbers in a column

I don't think code should be necessary here but let me know if you'd like it anyways.
I had to delete an entire set of entries in one of my tables. These entries were being organized by a integer value that increased with intervals. Is there a way that I can write a query so that all the values in a particular column will update with a -1 value?
So for example, lets say I had this table
| Red | 1 |
| Orange | 2 |
| Yellow | 3 |
| Green | 4 |
| Cyan | 6 |
| Blue | 7 |
| Purple | 8 |
| Violet | 9 |
could I write a single query so that cyan - violet's numbers all subtracted by one rather than doing a unique update for every entry?
| Red | 1 |
| Orange | 2 |
| Yellow | 3 |
| Green | 4 |
| Cyan | 5 |
| Blue | 6 |
| Purple | 7 |
| Violet | 8 |
Use Cte to update and Row_number() to generate sequential numbers
; WITH cte
AS (SELECT ( Row_number()OVER( ORDER BY id) )rn,*
FROM yourtable)
UPDATE cte
SET id = rn