Name of customers who are of legal age (18 years) Postgresql - sql

I have a question about how to return only customers over 18 years old.
I'm just getting back from the customers
SELECT
custname,
(2021 - part_date ('year', custdatebirth)) AS age
FROM
customers;

If you want to filter the rows, use a where clause. If you want to filter by age, you can use date comparisons:
where custdatebirth < current_date - interval '18 year'
Note that phrasing the condition this way makes it friendlier to indexes.

This solution of this could be as follow
SELECT
custname,
custdatebirth
FROM
customers
WHERE
EXTRACT(YEAR, custdatebirth) <= EXTRACT(YEAR FROM CURRENT_DATE)-18;

Related

Find Customers Who Shop at Multiple Stores

I need a query that will give me a count of customers who have shopped at multiple store locations within the last 3 years.
I have formulated the following query, but it's not what I need to know:
SELECT STORE_ID, CUSTOMER_ID, COUNT(DISTINCT CUSTOMER_ID) as SERVICE_COUNT
From SALES INNER JOIN
STORE_DETAILS
ON trim(STORE_ID) = trim(STORE_ID)
WHERE (CURRENT_DATE - cast(SALE_DATE AS DATE format 'mm/dd/yyyy')) < 1095
ORDER BY 1,2
Group by 1,2
HAVING COUNT(DISTINCT SALE_DATE) > 1
If you want customers at multiple stores, then something like:
SELECT CUSTOMER_ID
FROM SALES INNER JOIN
STORE_DETAILS
ON trim(STORE_ID) = trim(STORE_ID)
WHERE (CURRENT_DATE - cast(SALE_DATE AS DATE format 'mm/dd/yyyy')) < 1095
GROUP BY 1
HAVING COUNT(DISTINCT STORE_ID) > 1;
I don't understand your date expression, but presumably you know what it is supposed to be doing.
Optimized version of Gordon's query based on your comments:
Often, the store_id has trailing spaces not allowing a true match
Comparing strings ignores trailing spaces. As long as there are no leading spaces (which is a worst case and should be fixed during load) you don't have to TRIM (it's quite bad for performance).
The datatype for SALE_DATE is DATE
If it's a date there's no need for a CAST. Additionally the within three years logic can be simplified to avoid date calculattion on every row.
SELECT CUSTOMER_ID, COUNT(DISTINCT CUSTOMER_ID) as SERVICE_COUNT
FROM SALES
JOIN STORE_DETAILS
ON STORE_ID = STORE_ID
WHERE SALE_DATE >= ADD_MONTHS(CURRENT_DATE, -12*3)
GROUP BY 1
HAVING SERVICE_COUNT > 1
;

group by year month in postgresql

customer Date location
1 25Jan2018 texas
2 15Jan2018 texas
3 12Feb2018 Boston
4 19Mar2017 Boston.
I am trying to find out count of customers group by yearmon of Date column.Date column is of text data type
eg: In jan2018 ,the count is 2
I would do something like the following:
SELECT
date_part('year', formattedDate) as Year
,date_part('month', formattedDate) as Month
,count(*) as CustomerCountByYearMonth
FROM
(SELECT to_date(Date,'DDMonYYYY') as formattedDate from <table>) as tbl1
GROUP BY
date_part('year', formattedDate)
,date_part('month', formattedDate)
Any additional formatting for dates could be done on the inner query that will allow for adjustments in case some single digit days need to be padded or a month has four letters instead of three etc.
By converting to date type, you can properly order by date type and not alphabetical etc.
Optionally:
SELECT
Year
,Month
,count(*) as CustomerCountByYearMonth
FROM
(SELECT
date_part('year', to_date(Date,'DDMonYYYY')) as Year
,date_part('month', to_date(Date,'DDMonYYYY')) as Month
FROM <table>) as tbl1
GROUP BY
Year
,Month
You shouldn't store dates in a text column...
select substring(Date, length(Date)-6), count(*)
from tablename
group by substring(Date, length(Date)-6)
I thought #Jarlh asked a good question -- what about dates like January 1? Is it 01Jan2019 or 1Jan2019? If it can be either, perhaps a regex would work.
select
substring (date from '\d+(\D{3}\d{4})') as month,
count (distinct customer)
from t
group by month
The 'distinct customer' also presupposes you may have the same customer listed in the same month, but you only want to count it once. If that's not the case, just remove 'distinct.'
And, if you wanted the output in date format:
select
to_date (substring (date from '\d+(\D{3}\d{4})'), 'monyyyy') as month,
count (distinct customer)
from t
group by month
If it is a date column, you can truncate the date:
select date_trunc('month', date) as yyyymm, count(*)
from t
group by yyyymm
order by yyyymm;
I really read that the type was date. For a string, just use string functions:
select substr(date, 3, 7) as mmmyyyy, count(*)
from t
group by mmmyyyy;
Unfortunately, ordering doesn't work in this case. You should really be storing dates using the proper type.

Better way to write a SQL conditional [EDITED - Featured Needed]

I have the code below in my query, but I don't like it.
(
(year + 1) = date_part('YEAR', CURRENT_DATE) OR
year = date_part('YEAR', CURRENT_DATE) OR
(year - 1) = date_part('YEAR', CURRENT_DATE)
)
Exists a better form to write this conditional? The objective of this conditional is returns the values between the post year and the before year of today.
EDIT
I want a new feature:
SELECT DISTINCT ON (alias.year) alias.year, alias.another_column
FROM scheme.table AS alias
WHERE alias.active IS TRUE AND ABS(alias.year- date_part('YEAR', CURRENT_DATE)) IN (0,1)
ORDER BY alias.year, alias.another_column DESC;
The code above returns:
I want:
2017 - 8
2018 - 1
2019 - 1
This occurs because no exists any record with year 2019, but when not exists some year, I need return him with the value 1 in another column.
Try this, the between will match values in between the low and high value.
(date_part('YEAR', CURRENT_DATE) BETWEEN (year - 1) AND (year + 1))
EDIT:
To accomplish what you're talking about it may be easier to use a common table expression and a RIGHT OUTER JOIN the common table expression will fill with the years in the range (last year, this year, next year)
and will limit records to what is in the cte, even if records do not exist in the table for that year.
WITH RECURSIVE cte(year) AS(
VALUES (date_part('YEAR', CURRENT_DATE)-1)
UNION ALL
SELECT year+1 FROM cte WHERE year<date_part('YEAR', CURRENT_DATE)+1
)
SELECT DISTINCT ON (cte.year) alias.year, COALESCE(alias.another_column, 'value when blank') another_column
FROM scheme.table AS alias
RIGHT OUTER JOIN cte
ON alias.year = cte.year
WHERE alias.active IS TRUE
ORDER BY cte.year, alias.another_column DESC;
so records would show like this
2017 - 8
2018 - 1
2019 - value when blank
If you remove the COALESCE function it would look like this
2017 - 8
2018 - 1
2019 - NULL
EDIT:
As suggesting in comments you can also use generate_series() to create the common table expression. since date_part returns a double precision so you will have to CAST it to a integer I've used two methods so you have more options
WITH cte AS (
SELECT
generate_series(
date_part('YEAR', CURRENT_DATE)::integer-1
,CAST(date_part('YEAR', CURRENT_DATE) AS INTEGER)+1
) AS year
)
ABS(year - date_part('YEAR', CURRENT_DATE)) IN (0,1)
Not familiar with the specifics of this DBMS but that should work :
year BETWEEN (date_part('YEAR', CURRENT_DATE)-1) AND (date_part('YEAR', CURRENT_DATE)+1)
It also reads just like your requirement, which is usually a good thing for the person who will maintain your code in the future.

SQL query for multiple values of a column

I have db with names etc with date of birth. How can I get count of columns for all 12 months of the dates?
Exact code depends on the database you use; you should, somehow, "extract" month from date of birth in order to GROUP BY it.
In Oracle, you might have done it as
select to_char(date_of_birth), 'mon') dob_month,
count(*)
from your_table
group by to_char(date_of_birth, 'mon');
or
select extract(month from date_of_birth) dob_month,
count(*)
from your_table
group by extract(month from date_of_birth);

Find employee tenure for a company

I have written the following query to get the employees tenure yearwise.
Ie. grouped by "less than 1 year", "1-2 years", "2-3 years" and "greater than 3 years".
To get this, I compare with employee staffed end_date.
But I am not able to get the correct result when comparing with staffed end_date.
I have pasted the complete code below, but the count I am getting is not correct.
Some employee who worked for more than 2 years is falling under <1 year column.
DECLARE #Project_Id Varchar(10)='ITS-004275';
With Cte_Dates(Period,End_date,Start_date,Project_Id)
As
(
SELECT '<1 Year' AS Period, GETDATE() AS End_Date,DATEADD(YY,-1,GETDATE()) AS Start_date,#Project_Id AS Project_Id
UNION
SELECT '1-2 Years', DATEADD(YY,-1,GETDATE()),DATEADD(YY,-2,GETDATE()),#Project_Id
UNION
SELECT '2-3 Years', DATEADD(YY,-2,GETDATE()),DATEADD(YY,-3,GETDATE()),#Project_Id
UNION
SELECT '>3 Years', DATEADD(YY,-3,GETDATE()),'',#Project_Id
),
--select * from Cte_Dates
--ORDER BY Start_date DESC
Cte_Staffing(PROJECT_ID,EMP_ID,END_DATE) AS
(
SELECT FK_Project_ID,EMP_ID,MAX(End_Date)AS END_DATE FROM DP_Project_Staffing
WHERE FK_Project_ID=#Project_Id
GROUP BY FK_Project_ID,Emp_ID
)
SELECT D.PROJECT_ID,D.Start_date,D.End_date,COUNT(S.EMP_ID) AS Count,D.Period
FROM Cte_Staffing S
RIGHT JOIN Cte_Dates D
ON D.Project_Id=S.PROJECT_ID
AND S.END_DATE<D.End_date AND S.END_DATE>D.Start_date
GROUP BY D.PROJECT_ID,D.Start_date,D.End_date,D.Period
i think this will solve the problem
as you can see, you should use is like this:
DATEADD(year, -1, GETDATE())
you should also get the GETDATE() to a parameter
I find your query logic a little bit messy. Why don't you just compute the total period for every employee and use CASE clause? I can help you with code if you'll give me DP_Project_Staffing table structure. Do you have begin_date field in it?
You are taking the MAX(End_date) of the CTE staffing table. In that case, when an employee has several entries, only the most recent will apply. You want to use MIN instead.
Like this:
Cte_Staffing(PROJECT_ID,EMP_ID,END_DATE) AS
(
SELECT FK_Project_ID, EMP_ID, MIN(End_Date)AS END_DATE
FROM DP_Project_Staffing
...
Re-reading your question, you probably don't want the staffing end_date for tenure calculation; you'd want to use the start_date. (Or whatever the column is called in DP_Project_Staffing)
I would also change the WHERE/JOIN clause to be inclusive on one of the sides, so you have either
AND S.END_DATE <= D.End_date AND S.END_DATE > D.Start_date
or
AND S.END_DATE < D.End_date AND S.END_DATE >= D.Start_date
Since you are using miliseconds in the date-comparison it won't make any difference in this case. However, should you change the granularity to be only the date, which would make more sense, you would lose all records where the employee started exactly 1 year, 2 years, etc. ago.
SELECT FK_Project_ID,E.Emp_ID,MIN(Start_Date) AS Emp_Start_Date ,MAX(End_Date) AS Emp_End_Date,
E.Competency,E.First_Name+' '+E.Last_Name+' ('+E.Emp_Id+')' as Name,'Period'=
CASE
WHEN DATEDIFF(MONTH,MIN(Start_Date),MAX(End_Date))<=12 THEN '<1 Year'
WHEN DATEDIFF(MONTH,MIN(Start_Date),MAX(End_Date))>12 AND DATEDIFF(MONTH,MIN(Start_Date),MAX(End_Date))<=24 THEN '1-2 Years'
WHEN DATEDIFF(MONTH,MIN(Start_Date),MAX(End_Date))>24 AND DATEDIFF(MONTH,MIN(Start_Date),MAX(End_Date))<=36 THEN '2-3 Years'
WHEN DATEDIFF(MONTH,MIN(Start_Date),MAX(End_Date))>36 THEN '>3 Years'
ELSE 'NA'
END
FROM DP_Project_Staffing PS
LEFT OUTER JOIN DP_Ext_Emp_Master E
ON E.Emp_Id=PS.Emp_ID
WHERE FK_Project_ID=#PROJ_ID
GROUP BY FK_Project_ID,E.Emp_ID,E.Competency,First_Name,Last_Name