Pregunta

Estamos creando una aplicación que almacena " horas de operación " para diversas empresas. ¿Cuál es la forma más fácil de representar estos datos para que pueda verificar fácilmente si un elemento está abierto?

Algunas opciones:

  • Segmenta los bloques (cada 15 minutos) que puedes marcar " abrir / cerrado " La verificación implica ver si el " abrir " el bit se establece para el tiempo deseado (un poco como un horario de trenes).
  • Almacenar una lista de rangos de tiempo (11 am-2pm, 5-7pm, etc.) y verificar si el tiempo actual cae dentro de un rango específico (esto es lo que hace nuestro cerebro al analizar las cadenas anteriores).

¿Alguien tiene experiencia en almacenar y consultar información de horarios y algún consejo para dar?

(Hay todo tipo de casos locos en las esquinas, como "cerrado el primer martes del mes", pero lo dejaremos para otro día).

¿Fue útil?

Solución

almacena cada bloque de tiempo contiguo como tiempo de inicio y duración; esto hace que sea más fácil verificar cuándo las horas cruzan los límites de la fecha

si está seguro de que las horas de operación nunca cruzarán los límites de las fechas (es decir, nunca habrá una venta abierta toda la noche o un evento de maratón de 72 horas, etc.) entonces las horas de inicio / finalización serán suficientes

Otros consejos

La solución más flexible podría ser utilizar el enfoque de bitset. Hay 168 horas en una semana, por lo que hay 672 períodos de 15 minutos. Eso es solo 84 bytes de espacio, lo que debería ser tolerable.

Yo usaría una tabla como esta:

BusinessID | weekDay | OpenTime | CloseTime 
---------------------------------------------
     1          1        9           13
     1          2        5           18
     1          3        5           18
     1          4        5           18
     1          5        5           18
     1          6        5           18
     1          7        5           18

Aquí, tenemos un negocio que tiene un horario regular de 5 a 6, pero menos horas los domingos.

Una consulta para si abierto sería (psuedo-sql)

SELECT @isOpen = CAST
   (SELECT 1 FROM tblHours 
       WHERE BusinessId = @id AND weekDay = @Day 
       AND CONVERT(Currentime to 24 hour) IS BETWEEN(OpenTime,CloseTime)) AS BIT;

Si necesita almacenar casos perimetrales, entonces solo tiene 365 entradas, una por día ... realmente no es mucho en el gran esquema de las cosas, coloque un índice en la columna del día y en la columna de ID de negocio.

No olvide almacenar la zona horaria de las empresas en una tabla separada (¡normalizar!) y realice una transformación entre su hora y esta antes de realizar estas comparaciones.

Creo que personalmente iría por un comienzo + final, ya que haría todo más flexible. Una buena pregunta sería: ¿cuál es la probabilidad de que el tamaño del bloque cambie en cierto punto? Luego, elija la solución que mejor se adapte a su situación (si es probable que cambie, iría por los plazos definidos).

Puede almacenarlos como un intervalo de tiempo y usar segmentos en su aplicación. De esa manera, tendrá la entrada fácil usando bloques, mientras mantiene la flexibilidad para cambiar en su almacén de datos.

Para agregar a lo que Johnathan Holland dijo , permitiría múltiples entradas para el mismo día.

También permitiría el tiempo decimal u otra columna para los minutos.

¿Por qué? muchos restaurantes y algunas empresas, y muchas empresas de todo el mundo almuerzan o descansan por la tarde. Además, muchos restaurantes (2 que conozco cerca de mi casa cierran en intervalos impares no de 15 incrementos. Uno cierra a las 9:40 PM los domingos, y uno cierra a la 1:40 AM.

También está el tema de los días festivos, como las tiendas que cierran temprano el día de acción de gracias, por ejemplo, por lo que necesita una anulación basada en el calendario.

Quizás lo que se puede hacer es una fecha / hora de apertura, fecha y hora de cierre, como esto:

businessID  | datetime              | type
==========================================
        1     10/1/2008 10:30:00 AM    1
        1     10/1/2008 02:45:00 PM    0
        1     10/1/2008 05:15:00 PM    1
        1     10/2/2008 02:00:00 AM    0
        1     10/2/2008 10:30:00 AM    1

etc. (escriba: 1 está abierto y 0 cerrado)

Y tenga todos los días en los próximos 1 o 2 años precalculados con 1-2 años de anticipación. Tenga en cuenta que solo tendría 3 columnas: int, fecha / hora / bit, por lo que el consumo de datos debería ser mínimo.

Esto también le permitirá modificar fechas específicas para horas impares para días especiales, a medida que se conozcan.

También se ocupa de cruzar durante la medianoche, así como las conversiones de 12/24 horas.

También es agnóstico de la zona horaria. Si almacena la hora de inicio y la duración, cuando calcule la hora de finalización, ¿su máquina le dará la hora ajustada de TZ? ¿Es eso lo que quieres? Más código.

en cuanto a la consulta de estado abierto-cerrado: consulte la fecha y hora en cuestión,

select top 1 type from thehours where datetimefield<=somedatetime and businessID = somebusinessid order by datetime desc

luego mira " escribe " ;. si es uno, está abierto, si es 0, está cerrado.

PS: Estuve en el comercio minorista durante 10 años. Así que estoy familiarizado con los problemas de la locura de las pequeñas empresas.

Está bien, voy a lanzar esto para que valga la pena.

Necesito manejar bastantes cosas.

  • Consulta rápida / Performante
  • Cualquier incremento de tiempo, 9:01 PM, 12:14, etc.
  • Internacional (?): no estoy seguro de si se trata de un problema, incluso con las zonas horarias, al menos en mi caso, pero alguien más versado aquí se siente libre de participar
  • Abierto: cierre hasta el día siguiente (abierto al mediodía, cerrado a las 2:00 AM)
  • Tiempos múltiples / día
  • Capacidad para anular días específicos (vacaciones, lo que sea)
  • Capacidad para que las sustituciones sean recurrentes
  • Capacidad para consultar cualquier punto en el tiempo y abrir negocios (ahora, tiempo futuro, tiempo pasado)
  • Capacidad para excluir fácilmente los resultados de negocios que cierran pronto (filtre los negocios que cierran en 30 minutos, no quiere que los usuarios sean los mismos que aparecen 5 minutos antes de cerrar en la industria de alimentos / bebidas)

Me gustan muchos de los enfoques presentados y tomo prestados algunos de ellos. En mi sitio web, proyecto, sea lo que sea lo que deba tener en cuenta, es posible que tenga millones de negocios y que algunos de los enfoques aquí no me parezcan bien personalmente.

Esto es lo que propongo para un algoritmo y estructura.

Tenemos que hacer algunas suposiciones concretas, en todo el mundo, en cualquier lugar, en cualquier momento: Hay 7 días en una semana. Hay 1440 minutos en un día. Hay un número finito de permutaciones de minutos de apertura / cierre que son posibles.

Suposiciones no concretas pero decentes: Muchas permutaciones de minutos abiertos / cerrados se compartirán entre las empresas, lo que reducirá las permutaciones totales realmente almacenadas. Hubo un tiempo en mi vida en el que podía calcular fácilmente las posibles combinaciones reales de este enfoque, pero si alguien pudiera ayudarlo / piensa que sería útil, sería genial.

Propongo 3 tablas: Antes de que dejes de leer, considera que en el mundo real 2 de estas tablas será suficientemente pequeña como caché. Este enfoque no será para todos, debido a la gran complejidad del código requerido para interpretar una UI para el modelo de datos y, de nuevo, volver a ser necesario. Su kilometraje y necesidades pueden variar. Este es un intento de una solución de nivel empresarial razonable, sea lo que sea lo que signifique.

Tabla HoursOfOperations

ID | ABIERTO (minuto del día) | CERRAR (minuto del día)


1 | 360 | 1020 (ejemplo: 9 AM - 5 PM)

2 | 365 | 1021 (ejemplo: edge-case 9:05 AM - 5:01 PM (weirdos))

etc.

HoursOfOperations no se preocupa por los días, solo abre, cierra y es único. Solo puede haber una entrada por combinación de apertura / cierre. Ahora, dependiendo de su entorno, esta tabla completa se puede almacenar en caché o la hora actual del día, etc. En cualquier caso, no debe consultar esta tabla para cada operación. Dependiendo de su solución de almacenamiento, visualizo cada columna en esta tabla como indexada para el rendimiento. A medida que avanza el tiempo, es probable que esta tabla tenga una probabilidad exponencialmente inversa de INSERT (s). Sin embargo, en realidad, tratar con esta tabla debería ser principalmente una operación en proceso (RAM).

Business2HoursMap

Nota: en mi ejemplo, estoy almacenando " Día " como un campo / columna de bandera de bits. Esto se debe en gran medida a mis necesidades y al avance de LINQ / Flags Enums en C #. No hay nada que le impida expandir esto a campos de 7 bits. Ambos enfoques deben ser relativamente similares tanto en la lógica de almacenamiento como en el enfoque de consulta.

Otra nota: no voy a entrar en un argumento semántico en " cada tabla necesita una columna de ID de PK " ;, busque otro foro para eso.

BusinessID | HorasID | Día (o, si prefiere dividido en: BIT Monday, BIT Tuesday, ...)


1 | 1 | 1111111 (este negocio está abierto de 9 a 5 todos los días de la semana)

2 | 2 | 1111110 (este negocio está abierto 9:05 - 5:01 M-Sat (lunes = día 1)

La razón por la que esto es fácil de consultar es que siempre podemos determinar con bastante facilidad el MOTD (minuto del día) que buscamos. Si quiero saber lo que está abierto a las 5 PM mañana, tomo todas las ID DE Horas de Operación DONDE Cerrar > = 1020. A menos que esté buscando un rango de tiempo, Abrir se vuelve insignificante. Si no desea que las empresas cierren en la siguiente media hora, simplemente ajuste la hora entrante en consecuencia (busque 5:30 PM (1050), no 5:00 PM (1020). La segunda consulta sería, naturalmente, 'me dan todos los negocios con HoursID IN (1, 2, 3, 4, 5), etc. Esto probablemente debería generar una señal de advertencia ya que existen limitaciones para este enfoque. Sin embargo, si alguien puede responder a la pregunta de permutaciones reales de arriba, podremos quitar la bandera roja. Tenga en cuenta que solo necesitamos las posibles permutaciones en cualquier lado de la ecuación al mismo tiempo, ya sea abierto o cerrado.

Teniendo en cuenta que tenemos nuestra primera tabla en caché, es una operación rápida. La segunda operación es consultar esta tabla de fila potencialmente grande, pero estamos buscando columnas muy pequeñas (SMALLINT) con suerte indexadas.

Ahora, puedes estar viendo la complejidad en el lado del código de las cosas. Me dirijo principalmente a bares en mi proyecto particular, por lo que será muy seguro asumir que tendré un número considerable de empresas con horarios como " 11: 00 AM - 2:00 AM (el día siguiente) " . De hecho, serían 2 entradas tanto en la tabla HoursOfOperations como en la tabla Business2HoursMap. P.ej. un bar que está abierto de 11:00 a.m. a 2:00 p.m. tendrá 2 referencias a la tabla de Horas de operación 660 a 1440 (11:00 a.m. a medianoche) y de 0 a 120 (a medianoche a 2:00 a.m.). Esas referencias se reflejarán en los días reales en la tabla Business2HoursMap como 2 entradas en nuestro caso simplista, 1 entrada = todos los días Referencia de horas # 1, otra referencia de todos los días # 2. Espero que tenga sentido, ha sido un día largo.

Anulación en días especiales / vacaciones / lo que sea. Las anulaciones son por naturaleza, basadas en la fecha, no en el día de la semana. Creo que aquí es donde algunos de los enfoques intentan empujar la proverbial clavija redonda en un agujero cuadrado. Necesitamos otra mesa.

HoursID | BusinessID | Dia | Mes | Año

1 | 2 | 1 | 1 | NULL

Esto puede volverse más complejo si necesitas algo así como "cada segundo martes, esta compañía va a pescar durante 4 horas". Sin embargo, lo que esto nos permitirá hacer con bastante facilidad es permitir 1 - anulaciones, 2 - anulaciones recurrentes razonables. P.EJ. Si el año es nulo, todos los años en el día de Año Nuevo, este bar está abierto de 9:00 a. m. a 5:00 p. m., en línea con los ejemplos de datos anteriores. Es decir. - Si se fijó el año, es solo para 2013. Si el mes es nulo, es todos los primeros días del mes. Nuevamente, esto no manejará todos los escenarios de programación solo por columnas NULL, pero teóricamente, usted podría manejar casi cualquier cosa confiando en una larga secuencia de fechas absolutas si es necesario.

Una vez más, yo almacenaría en caché esta tabla cada día. Simplemente no puedo ver de forma realista las filas de esta tabla en una instantánea de un solo día que es muy grande, al menos para mis necesidades. Primero verificaría esta tabla, ya que está bien, una anulación y guardaría una consulta en la tabla Business2HoursMap mucho más grande en el lado del almacenamiento.

Problema interesante. Estoy realmente sorprendido de que esta sea la primera vez que realmente necesito pensar en esto. Como siempre, estoy muy interesado en diferentes ideas, enfoques o fallas en mi enfoque.

Los bloques de segmento son mejores, solo asegúrate de proporcionarle al usuario una manera fácil de configurarlos. Hacer clic y arrastrar es bueno.

Cualquier otro sistema (como rangos) será realmente molesto cuando cruces el límite de medianoche.

En cuanto a cómo los almacena, en los campos de bits de C ++ probablemente sería mejor. En la mayoría de los otros idiomas, la matriz podría ser mejor (mucho espacio desperdiciado, pero se ejecutaría más rápido y sería más fácil de comprender).

Pensaría un poco en esos casos de borde en este momento, porque informarán si tienes una configuración básica más una superposición o un almacenamiento estático completo de los tiempos de apertura o lo que sea.

Hay tantas excepciones, y de manera regular (como días de nieve, días festivos irregulares como Semana Santa, Viernes Santo), que si se espera que esto sea una representación confiable de la realidad (en lugar de una buena suposición), Tendremos que abordarlo muy pronto en la arquitectura.

¿Qué tal algo como esto?

Tabla de horas de tienda

Business_id (int)
Start_Time (time)
End_Time (time)
Condition varchar/string
Open bit

'Condición' es una expresión lambda (texto para una cláusula 'where'). Construye la consulta dinámicamente. Entonces, para un negocio en particular, seleccionas todos los tiempos de apertura / cierre

Let Query1 = select count(open) from store_hours where @t between start_time and end_time and open  = true and business_id = @id and (.. dynamically built expression)

Let Query2 = select count(closed) from store_hours where @t between start_time and end_time and open = false and business_id = @id and (.. dynamically built expression)

Así que al final, quieres algo como:

select cast(Query1 as bit) & ~cast(Query2 as bit)

Si el resultado de la última consulta es 1, la tienda está abierta en el momento t, de lo contrario, está cerrada.

Ahora solo necesita una interfaz amigable que pueda generar sus cláusulas where (expresiones lambda) para usted.

El único otro caso de esquina en el que puedo pensar es qué sucede si una tienda está abierta desde las 7 a.m. a las 2 a.m. en una fecha, pero se cierra a las 11 p.m. en la siguiente fecha. Su sistema debería poder manejar eso también dividiendo inteligentemente los tiempos entre los dos días.

Seguramente no hay necesidad de conservar la memoria aquí, sino tal vez la necesidad de un código limpio y comprensible. " Bit twiddling " no es, en mi humilde opinión, el camino a seguir.

Aquí necesitamos un contenedor de conjuntos, que contiene cualquier número de elementos únicos y puede determinar de forma rápida y sencilla si un elemento es miembro o no. La configuración requiere atención, pero en el uso de rutina, una sola línea de código comprendido simplemente determina si está abierto o cerrado

Concepto: Asigne un número de índice a cada bloque de 15 minutos, comenzando en, digamos, el domingo a medianoche.

Inicializar: Inserte en un conjunto el número de índice de cada bloque de 15 minutos cuando esté abierto. (Suponiendo que está abierto menos horas de las que está cerrado).

Usar: Reste del tiempo interesante, en minutos, medianoche del domingo anterior y divida entre 15. Si este número está presente en el conjunto, está abierto.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top